From e29f3eadd95887a8d336af3dfacd00d244e06f9b Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Tue, 14 Mar 2017 19:11:26 +0000 Subject: [PATCH 01/83] fix all instances of var --- build/Program.cs | 58 +++++++++---------- .../Brushes/ImageBrush{TColor}.cs | 2 +- .../Brushes/RecolorBrush{TColor}.cs | 2 +- src/ImageSharp.Drawing/Pens/Pen{TColor}.cs | 22 +++---- src/ImageSharp/Colors/PackedPixel/Argb.cs | 4 +- .../Components/Decoder/JpegBlockProcessor.cs | 4 +- .../Jpeg/Components/Decoder/JpegPixelArea.cs | 2 +- .../Components/Decoder/JpegScanDecoder.cs | 4 +- src/ImageSharp/IO/EndianBinaryWriter.cs | 2 +- src/ImageSharp/Image/Image{TColor}.cs | 2 +- .../Drawing/DrawBeziers.cs | 2 +- .../Drawing/DrawLines.cs | 2 +- .../Drawing/DrawPolygon.cs | 2 +- .../Drawing/FillWithPattern.cs | 2 +- .../Image/MultiImageBenchmarkBase.cs | 20 +++---- .../Colors/BulkPixelOperationsTests.cs | 2 +- .../Colors/ColorConstructorTests.cs | 34 +++++------ .../Colors/ColorEqualityTests.cs | 20 +++---- .../Colors/ColorPackingTests.cs | 18 +++--- .../Common/PinnedBufferTests.cs | 2 +- tests/ImageSharp.Tests/ConfigurationTests.cs | 2 +- .../Drawing/LineComplexPolygonTests.cs | 4 +- .../Drawing/SolidComplexPolygonTests.cs | 2 +- .../Drawing/SolidPolygonTests.cs | 6 +- .../Drawing/Text/GlyphBuilder.cs | 6 +- .../Drawing/Text/OutputText.cs | 2 +- .../Formats/Gif/GifDecoderTests.cs | 6 +- .../Formats/Gif/GifEncoderTests.cs | 4 +- .../Formats/Jpg/Block8x8FTests.cs | 40 ++++++------- .../Formats/Jpg/JpegDecoderTests.cs | 4 +- .../Formats/Jpg/JpegEncoderTests.cs | 4 +- .../Jpg/ReferenceImplementationsTests.cs | 4 +- .../Formats/Jpg/YCbCrImageTests.cs | 2 +- .../Formats/Png/PngDecoderTests.cs | 6 +- tests/ImageSharp.Tests/Image/ImageTests.cs | 20 +++---- .../Profiles/Exif/ExifProfileTests.cs | 2 +- .../Exif/ExifTagDescriptionAttributeTests.cs | 2 +- .../Processors/Filters/ResizeTests.cs | 4 +- tests/ImageSharp.Tests/TestFile.cs | 2 +- tests/ImageSharp.Tests/TestFont.cs | 2 +- .../Attributes/ImageDataAttributeBase.cs | 12 ++-- .../Attributes/WithFileCollectionAttribute.cs | 6 +- .../Attributes/WithMemberFactoryAttribute.cs | 16 ++--- .../ImageProviders/FileProvider.cs | 6 +- .../ImageProviders/SolidProvider.cs | 2 +- .../TestUtilities/ImagingTestCaseUtility.cs | 4 +- .../TestUtilities/TestUtilityExtensions.cs | 10 ++-- .../Tests/TestImageProviderTests.cs | 20 +++---- .../Tests/TestUtilityExtensionsTests.cs | 18 +++--- 49 files changed, 212 insertions(+), 212 deletions(-) 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/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs index 2707c00642..732cd4f8be 100644 --- a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs @@ -91,7 +91,7 @@ namespace ImageSharp.Drawing.Brushes { 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; diff --git a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs index 542c3cfed6..db8f3705e3 100644 --- a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs @@ -124,7 +124,7 @@ namespace ImageSharp.Drawing.Brushes 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, diff --git a/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs b/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs index 79a5d6b15a..ffcc5ee757 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 = Math.Min(distanceFromStart, distanceFromEnd); - var distanceAcross = closestEdge; + float distanceAcross = closestEdge; if (distanceWAway > 0) { 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/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/IO/EndianBinaryWriter.cs b/src/ImageSharp/IO/EndianBinaryWriter.cs index 027c2b1f06..3b2028afdd 100644 --- a/src/ImageSharp/IO/EndianBinaryWriter.cs +++ b/src/ImageSharp/IO/EndianBinaryWriter.cs @@ -52,7 +52,7 @@ namespace ImageSharp.IO /// public EndianBinaryWriter(Endianness endianness, Stream stream, Encoding encoding) { - var bitConverter = EndianBitConverter.GetConverter(endianness); + EndianBitConverter bitConverter = EndianBitConverter.GetConverter(endianness); // TODO: Use Guard if (bitConverter == null) diff --git a/src/ImageSharp/Image/Image{TColor}.cs b/src/ImageSharp/Image/Image{TColor}.cs index 27dee54342..4b22424da8 100644 --- a/src/ImageSharp/Image/Image{TColor}.cs +++ b/src/ImageSharp/Image/Image{TColor}.cs @@ -168,7 +168,7 @@ namespace ImageSharp : base(configuration) { Guard.NotNull(filePath, nameof(filePath)); - using (var fs = File.OpenRead(filePath)) + using (FileStream fs = File.OpenRead(filePath)) { this.Load(fs, options); } 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/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/Image/MultiImageBenchmarkBase.cs b/tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs index 26aad07b8d..4fee634f5f 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,12 +147,12 @@ 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); @@ -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.Tests/Colors/BulkPixelOperationsTests.cs b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs index 6621292ec2..fa950c4cf2 100644 --- a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs @@ -368,7 +368,7 @@ namespace ImageSharp.Tests.Colors 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/PinnedBufferTests.cs b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs index 3688763b9a..26b529f6a5 100644 --- a/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs +++ b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs @@ -78,7 +78,7 @@ using (PinnedBuffer buffer = new PinnedBuffer(a)) { - var arrayPtr = buffer.Slice(); + BufferPointer arrayPtr = buffer.Slice(); Assert.Equal(a, arrayPtr.Array); Assert.Equal(0, arrayPtr.Offset); diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index 883d235673..4f48c13232 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -21,7 +21,7 @@ namespace ImageSharp.Tests [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/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/SolidComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs index cd3182d6c2..ce13d9f0f6 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)) { diff --git a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs index 9c6c6d2342..54c4af38b1 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs @@ -209,7 +209,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 +228,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 +248,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/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/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..a140b7a3cf 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 }; @@ -42,7 +42,7 @@ namespace ImageSharp.Tests [Fact] public void Encode_IgnoreMetadataIsTrue_CommentsAreNotWritten() { - var options = new GifEncoderOptions() + GifEncoderOptions options = new GifEncoderOptions() { IgnoreMetadata = true }; 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..5723f9b234 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -125,7 +125,7 @@ namespace ImageSharp.Tests [Fact] public void Decode_IgnoreMetadataIsFalse_ExifProfileIsRead() { - var options = new DecoderOptions() + DecoderOptions options = new DecoderOptions() { IgnoreMetadata = false }; @@ -141,7 +141,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..c97eb14619 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 }; @@ -99,7 +99,7 @@ namespace ImageSharp.Tests [Fact] public void Encode_IgnoreMetadataIsTrue_ExifProfileIgnored() { - var options = new JpegEncoderOptions() + JpegEncoderOptions options = new JpegEncoderOptions() { IgnoreMetadata = true }; 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/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index 0ed724fadc..b0a031a780 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -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/MetaData/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs index 8ec57057f2..785d9dcfc2 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs @@ -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); 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/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..939b1254e6 100644 --- a/tests/ImageSharp.Tests/TestFile.cs +++ b/tests/ImageSharp.Tests/TestFile.cs @@ -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/TestFont.cs b/tests/ImageSharp.Tests/TestFont.cs index 3a5bb2b2c1..e7ef63b891 100644 --- a/tests/ImageSharp.Tests/TestFont.cs +++ b/tests/ImageSharp.Tests/TestFont.cs @@ -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/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/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index cd403caed3..7975f9b7e3 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -37,13 +37,13 @@ namespace ImageSharp.Tests 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); }); diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs index 855374f552..1593014ae5 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs @@ -38,7 +38,7 @@ namespace ImageSharp.Tests 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); diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs index 38429b2786..bcccd1b44d 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -81,11 +81,11 @@ namespace ImageSharp.Tests { string path = this.GetTestOutputFileName(extension); - var format = GetImageFormatByExtension(extension); + 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); } 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); From 418218a06b661e029df7ce3f54e7c6292ce9812b Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Tue, 14 Mar 2017 19:09:02 +0000 Subject: [PATCH 02/83] Add IFileSystem --- src/ImageSharp/Configuration.cs | 8 +++ src/ImageSharp/IO/IFileSystem.cs | 31 +++++++++++ src/ImageSharp/IO/LocalFileSystem.cs | 27 ++++++++++ src/ImageSharp/Image/Image{TColor}.cs | 42 ++++++++++----- tests/ImageSharp.Tests/ConfigurationTests.cs | 12 ++++- tests/ImageSharp.Tests/IO/LocalFileSystem.cs | 53 +++++++++++++++++++ .../ImageSharp.Tests/Image/ImageSaveTests.cs | 48 +++++++++++++++++ .../Image/SaveWatchingImage.cs | 39 ++++++++++++++ 8 files changed, 246 insertions(+), 14 deletions(-) create mode 100644 src/ImageSharp/IO/IFileSystem.cs create mode 100644 src/ImageSharp/IO/LocalFileSystem.cs create mode 100644 tests/ImageSharp.Tests/IO/LocalFileSystem.cs create mode 100644 tests/ImageSharp.Tests/Image/ImageSaveTests.cs create mode 100644 tests/ImageSharp.Tests/Image/SaveWatchingImage.cs diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index e9120aa479..053ec02026 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. @@ -53,6 +54,13 @@ namespace ImageSharp /// internal int MaxHeaderSize { get; private set; } +#if !NETSTANDARD1_1 + /// + /// 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/IO/IFileSystem.cs b/src/ImageSharp/IO/IFileSystem.cs new file mode 100644 index 0000000000..1ce4f25f4c --- /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. +// + +using System.IO; + +namespace ImageSharp.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 OpenWrite(string path); + } +#endif +} diff --git a/src/ImageSharp/IO/LocalFileSystem.cs b/src/ImageSharp/IO/LocalFileSystem.cs new file mode 100644 index 0000000000..e32fc67338 --- /dev/null +++ b/src/ImageSharp/IO/LocalFileSystem.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace ImageSharp.IO +{ +#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 OpenWrite(string path) + { + return File.OpenWrite(path); + } + } +#endif +} diff --git a/src/ImageSharp/Image/Image{TColor}.cs b/src/ImageSharp/Image/Image{TColor}.cs index 4b22424da8..0c4aaa7849 100644 --- a/src/ImageSharp/Image/Image{TColor}.cs +++ b/src/ImageSharp/Image/Image{TColor}.cs @@ -168,7 +168,7 @@ namespace ImageSharp : base(configuration) { Guard.NotNull(filePath, nameof(filePath)); - using (FileStream fs = File.OpenRead(filePath)) + using (Stream fs = File.OpenRead(filePath)) { this.Load(fs, options); } @@ -349,7 +349,7 @@ namespace ImageSharp public Image Save(Stream stream, IEncoderOptions options) { Guard.NotNull(stream, nameof(stream)); - this.CurrentImageFormat.Encoder.Encode(this, stream, options); + this.SaveInternal(stream, this.CurrentImageFormat?.Encoder, options); return this; } @@ -373,9 +373,10 @@ 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); + + this.SaveInternal(stream, format.Encoder, options); + return this; } @@ -405,15 +406,7 @@ namespace ImageSharp /// public Image Save(Stream stream, IImageEncoder encoder, IEncoderOptions options) { - 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; - } + this.SaveInternal(stream, encoder, options); return this; } @@ -569,6 +562,29 @@ namespace ImageSharp return target; } + /// + /// Internally saves the image to the given stream using the given image encoder and options. + /// Can be used by overridden by tests to verify save opperations. + /// + /// The stream to save the image to. + /// The encoder to save the image with. + /// The options for the encoder. + /// Thrown if the stream or encoder is null. + internal virtual void SaveInternal(Stream stream, IImageEncoder encoder, IEncoderOptions options) + { + Guard.NotNull(stream, nameof(stream)); + Guard.NotNull(encoder, nameof(encoder)); + + long startOfStream = stream.Position; + encoder.Encode(this, stream, options); + + // Reset to the start of the stream. + if (stream.CanSeek) + { + stream.Position = startOfStream; + } + } + /// /// Creates a new from this instance /// diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index 4f48c13232..043c3d3f1f 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,6 +18,16 @@ namespace ImageSharp.Tests /// public class ConfigurationTests { + [Fact] + public void DefaultsToLocalFileSystem() + { + var configuration = Configuration.CreateDefaultInstance(); + + ImageSharp.IO.IFileSystem fs = configuration.FileSystem; + + Assert.IsType(fs); + } + [Fact] public void IfAutoloadWellknwonFormatesIsTrueAllFormateAreLoaded() { diff --git a/tests/ImageSharp.Tests/IO/LocalFileSystem.cs b/tests/ImageSharp.Tests/IO/LocalFileSystem.cs new file mode 100644 index 0000000000..71fbaa3822 --- /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 (var r = new StreamReader(fs.OpenRead(path))) + { + string data = r.ReadToEnd(); + + Assert.Equal(testData, data); + } + + File.Delete(path); + } + + [Fact] + public void OpenWrite() + { + string path = Path.GetTempFileName(); + string testData = Guid.NewGuid().ToString(); + LocalFileSystem fs = new LocalFileSystem(); + + using (var r = new StreamWriter(fs.OpenWrite(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/ImageSaveTests.cs b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs new file mode 100644 index 0000000000..b6339ce8a9 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs @@ -0,0 +1,48 @@ +// +// 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 SaveWatchingImage Image; + private readonly Mock fileSystem; + + public ImageSaveTests() + { + this.fileSystem = new Mock(); + this.Image = new SaveWatchingImage(1, 1, this.fileSystem.Object); + } + + [Fact] + public void SavePath() + { + Stream stream = new MemoryStream(); + this.fileSystem.Setup(x => x.OpenWrite("path")).Returns(stream); + this.Image.Save("path"); + + SaveWatchingImage.OperationDetails operation = this.Image.Saves.Single(); + Assert.Equal(stream, operation.stream); + Assert.Equal(this.Image.CurrentImageFormat.Encoder, operation.encoder); + Assert.Null(operation.options); + } + + public void Dispose() + { + this.Image.Dispose(); + } + } +} diff --git a/tests/ImageSharp.Tests/Image/SaveWatchingImage.cs b/tests/ImageSharp.Tests/Image/SaveWatchingImage.cs new file mode 100644 index 0000000000..010ad68294 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/SaveWatchingImage.cs @@ -0,0 +1,39 @@ + +namespace ImageSharp.Tests +{ + using System; + using System.IO; + using ImageSharp; + using Processing; + using System.Collections.Generic; + using ImageSharp.Formats; + using ImageSharp.IO; + + /// + /// Watches but does not actually run the processors against the image. + /// + /// + public class SaveWatchingImage : Image + { + public List Saves { get; } = new List(); + + public SaveWatchingImage(int width, int height, IFileSystem fs = null) + : base(width, height, Configuration.CreateDefaultInstance()) + { + //switch out the file system for tests + this.Configuration.FileSystem = fs ?? this.Configuration.FileSystem; + } + + internal override void SaveInternal(Stream stream, IImageEncoder encoder, IEncoderOptions options) + { + + } + + public struct OperationDetails + { + public Stream stream; + public IImageEncoder encoder; + public IEncoderOptions options; + } + } +} From b919105f16ad53864333760982073251d4aa99ac Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Wed, 15 Mar 2017 07:46:02 +0000 Subject: [PATCH 03/83] ensure all save operations pass along IEncoderOptions --- src/ImageSharp/Configuration.cs | 2 +- src/ImageSharp/IO/IFileSystem.cs | 10 +- src/ImageSharp/IO/LocalFileSystem.cs | 19 +- src/ImageSharp/Image/Image{TColor}.cs | 14 +- tests/ImageSharp.Tests/ConfigurationTests.cs | 2 +- tests/ImageSharp.Tests/IO/LocalFileSystem.cs | 6 +- .../ImageSharp.Tests/Image/ImageSaveTests.cs | 163 +++++++++++++++++- .../Image/SaveWatchingImage.cs | 9 +- 8 files changed, 195 insertions(+), 30 deletions(-) diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 053ec02026..e0eb21865e 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -56,7 +56,7 @@ namespace ImageSharp #if !NETSTANDARD1_1 /// - /// Helper for accessing the local file system. + /// Gets or sets the fielsystem helper for accessing the local file system. /// internal IFileSystem FileSystem { get; set; } = new LocalFileSystem(); #endif diff --git a/src/ImageSharp/IO/IFileSystem.cs b/src/ImageSharp/IO/IFileSystem.cs index 1ce4f25f4c..ee1ef84d7b 100644 --- a/src/ImageSharp/IO/IFileSystem.cs +++ b/src/ImageSharp/IO/IFileSystem.cs @@ -1,13 +1,13 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // -using System.IO; - namespace ImageSharp.IO { -#if !NETSTANDARD1_1 + using System.IO; + + #if !NETSTANDARD1_1 /// /// A simple interface representing the filesystem. /// @@ -25,7 +25,7 @@ namespace ImageSharp.IO /// /// Path to the file to open. /// A stream representing the file to open. - Stream OpenWrite(string path); + Stream Create(string path); } #endif } diff --git a/src/ImageSharp/IO/LocalFileSystem.cs b/src/ImageSharp/IO/LocalFileSystem.cs index e32fc67338..02a9914ead 100644 --- a/src/ImageSharp/IO/LocalFileSystem.cs +++ b/src/ImageSharp/IO/LocalFileSystem.cs @@ -1,11 +1,16 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// namespace ImageSharp.IO { -#if !NETSTANDARD1_1 + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + + #if !NETSTANDARD1_1 /// /// A wrapper around the local File apis. /// @@ -18,9 +23,9 @@ namespace ImageSharp.IO } /// - public Stream OpenWrite(string path) + public Stream Create(string path) { - return File.OpenWrite(path); + return File.Create(path); } } #endif diff --git a/src/ImageSharp/Image/Image{TColor}.cs b/src/ImageSharp/Image/Image{TColor}.cs index 0c4aaa7849..2c9f15393b 100644 --- a/src/ImageSharp/Image/Image{TColor}.cs +++ b/src/ImageSharp/Image/Image{TColor}.cs @@ -168,7 +168,8 @@ namespace ImageSharp : base(configuration) { Guard.NotNull(filePath, nameof(filePath)); - using (Stream fs = File.OpenRead(filePath)) + configuration = configuration ?? Configuration.Default; + using (Stream fs = configuration.FileSystem.OpenRead(filePath)) { this.Load(fs, options); } @@ -439,7 +440,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); } /// @@ -465,10 +466,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); } /// @@ -494,9 +492,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 diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index 043c3d3f1f..c749239d71 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -21,7 +21,7 @@ namespace ImageSharp.Tests [Fact] public void DefaultsToLocalFileSystem() { - var configuration = Configuration.CreateDefaultInstance(); + Configuration configuration = Configuration.CreateDefaultInstance(); ImageSharp.IO.IFileSystem fs = configuration.FileSystem; diff --git a/tests/ImageSharp.Tests/IO/LocalFileSystem.cs b/tests/ImageSharp.Tests/IO/LocalFileSystem.cs index 71fbaa3822..472d643cd3 100644 --- a/tests/ImageSharp.Tests/IO/LocalFileSystem.cs +++ b/tests/ImageSharp.Tests/IO/LocalFileSystem.cs @@ -22,7 +22,7 @@ namespace ImageSharp.Tests.IO LocalFileSystem fs = new LocalFileSystem(); - using (var r = new StreamReader(fs.OpenRead(path))) + using (StreamReader r = new StreamReader(fs.OpenRead(path))) { string data = r.ReadToEnd(); @@ -33,13 +33,13 @@ namespace ImageSharp.Tests.IO } [Fact] - public void OpenWrite() + public void Create() { string path = Path.GetTempFileName(); string testData = Guid.NewGuid().ToString(); LocalFileSystem fs = new LocalFileSystem(); - using (var r = new StreamWriter(fs.OpenWrite(path))) + using (StreamWriter r = new StreamWriter(fs.Create(path))) { r.Write(testData); } diff --git a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs index b6339ce8a9..172a14dc2a 100644 --- a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs @@ -20,10 +20,12 @@ namespace ImageSharp.Tests { private readonly SaveWatchingImage Image; private readonly Mock fileSystem; + private readonly IEncoderOptions encoderOptions; public ImageSaveTests() { this.fileSystem = new Mock(); + this.encoderOptions = new Mock().Object; this.Image = new SaveWatchingImage(1, 1, this.fileSystem.Object); } @@ -31,15 +33,170 @@ namespace ImageSharp.Tests public void SavePath() { Stream stream = new MemoryStream(); - this.fileSystem.Setup(x => x.OpenWrite("path")).Returns(stream); - this.Image.Save("path"); + this.fileSystem.Setup(x => x.Create("path.png")).Returns(stream); + this.Image.Save("path.png"); SaveWatchingImage.OperationDetails operation = this.Image.Saves.Single(); Assert.Equal(stream, operation.stream); - Assert.Equal(this.Image.CurrentImageFormat.Encoder, operation.encoder); + Assert.IsType(operation.encoder); Assert.Null(operation.options); } + [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); + + SaveWatchingImage.OperationDetails operation = this.Image.Saves.Single(); + Assert.Equal(stream, operation.stream); + Assert.IsType(operation.encoder); + Assert.Equal(this.encoderOptions, operation.options); + } + + [Fact] + public void SavePathWithEncoder() + { + Stream stream = new MemoryStream(); + this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream); + + this.Image.Save("path.jpg", new BmpEncoder()); + + SaveWatchingImage.OperationDetails operation = this.Image.Saves.Single(); + Assert.Equal(stream, operation.stream); + Assert.IsType(operation.encoder); + Assert.Null(operation.options); + } + + [Fact] + public void SavePathWithEncoderAndOptions() + { + Stream stream = new MemoryStream(); + this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream); + + this.Image.Save("path.jpg", new BmpEncoder(), this.encoderOptions); + + SaveWatchingImage.OperationDetails operation = this.Image.Saves.Single(); + Assert.Equal(stream, operation.stream); + Assert.IsType(operation.encoder); + Assert.Equal(this.encoderOptions, operation.options); + } + + + + [Fact] + public void SavePathWithFormat() + { + Stream stream = new MemoryStream(); + this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream); + + this.Image.Save("path.jpg", new GifFormat()); + + SaveWatchingImage.OperationDetails operation = this.Image.Saves.Single(); + Assert.Equal(stream, operation.stream); + Assert.IsType(operation.encoder); + Assert.Null(operation.options); + } + + [Fact] + public void SavePathWithFormatAndOptions() + { + Stream stream = new MemoryStream(); + this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream); + + this.Image.Save("path.jpg", new BmpFormat(), this.encoderOptions); + + SaveWatchingImage.OperationDetails operation = this.Image.Saves.Single(); + Assert.Equal(stream, operation.stream); + Assert.IsType(operation.encoder); + Assert.Equal(this.encoderOptions, operation.options); + } + + /// + /// ///////////////////////////////////////////////////////////// + /// + /// + + [Fact] + public void SaveStream() + { + Stream stream = new MemoryStream(); + this.Image.Save(stream); + + SaveWatchingImage.OperationDetails operation = this.Image.Saves.Single(); + Assert.Equal(stream, operation.stream); + Assert.IsType(this.Image.CurrentImageFormat.Encoder.GetType(), operation.encoder); + Assert.Null(operation.options); + } + + [Fact] + public void SaveStreamWithOptions() + { + Stream stream = new MemoryStream(); + + this.Image.Save(stream, this.encoderOptions); + + SaveWatchingImage.OperationDetails operation = this.Image.Saves.Single(); + Assert.Equal(stream, operation.stream); + Assert.IsType(this.Image.CurrentImageFormat.Encoder.GetType(), operation.encoder); + + Assert.Equal(this.encoderOptions, operation.options); + } + + [Fact] + public void SaveStreamWithEncoder() + { + Stream stream = new MemoryStream(); + + this.Image.Save(stream, new BmpEncoder()); + + SaveWatchingImage.OperationDetails operation = this.Image.Saves.Single(); + Assert.Equal(stream, operation.stream); + Assert.IsType(operation.encoder); + Assert.Null(operation.options); + } + + [Fact] + public void SaveStreamWithEncoderAndOptions() + { + Stream stream = new MemoryStream(); + + this.Image.Save(stream, new BmpEncoder(), this.encoderOptions); + + SaveWatchingImage.OperationDetails operation = this.Image.Saves.Single(); + Assert.Equal(stream, operation.stream); + Assert.IsType(operation.encoder); + Assert.Equal(this.encoderOptions, operation.options); + } + + [Fact] + public void SaveStreamWithFormat() + { + Stream stream = new MemoryStream(); + + this.Image.Save(stream, new GifFormat()); + + SaveWatchingImage.OperationDetails operation = this.Image.Saves.Single(); + Assert.Equal(stream, operation.stream); + Assert.IsType(operation.encoder); + Assert.Null(operation.options); + } + + [Fact] + public void SaveStreamWithFormatAndOptions() + { + Stream stream = new MemoryStream(); + + this.Image.Save(stream, new BmpFormat(), this.encoderOptions); + + SaveWatchingImage.OperationDetails operation = this.Image.Saves.Single(); + Assert.Equal(stream, operation.stream); + Assert.IsType(operation.encoder); + Assert.Equal(this.encoderOptions, operation.options); + } + public void Dispose() { this.Image.Dispose(); diff --git a/tests/ImageSharp.Tests/Image/SaveWatchingImage.cs b/tests/ImageSharp.Tests/Image/SaveWatchingImage.cs index 010ad68294..e9b45da696 100644 --- a/tests/ImageSharp.Tests/Image/SaveWatchingImage.cs +++ b/tests/ImageSharp.Tests/Image/SaveWatchingImage.cs @@ -16,7 +16,7 @@ namespace ImageSharp.Tests public class SaveWatchingImage : Image { public List Saves { get; } = new List(); - + public SaveWatchingImage(int width, int height, IFileSystem fs = null) : base(width, height, Configuration.CreateDefaultInstance()) { @@ -26,7 +26,12 @@ namespace ImageSharp.Tests internal override void SaveInternal(Stream stream, IImageEncoder encoder, IEncoderOptions options) { - + this.Saves.Add(new OperationDetails + { + encoder = encoder, + options = options, + stream = stream + }); } public struct OperationDetails From 91c1a8f51a0653b18247d862f204f0d583b890ec Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Wed, 15 Mar 2017 07:55:48 +0000 Subject: [PATCH 04/83] use correct configuration object --- src/ImageSharp/Image/Image{TColor}.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Image/Image{TColor}.cs b/src/ImageSharp/Image/Image{TColor}.cs index 2c9f15393b..a64b515031 100644 --- a/src/ImageSharp/Image/Image{TColor}.cs +++ b/src/ImageSharp/Image/Image{TColor}.cs @@ -168,8 +168,8 @@ namespace ImageSharp : base(configuration) { Guard.NotNull(filePath, nameof(filePath)); - configuration = configuration ?? Configuration.Default; - using (Stream fs = configuration.FileSystem.OpenRead(filePath)) + + using (Stream fs = this.Configuration.FileSystem.OpenRead(filePath)) { this.Load(fs, options); } From d610a8536c5b64fd2b07b67d043bd28e4aa055fa Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Wed, 15 Mar 2017 12:53:40 +0000 Subject: [PATCH 05/83] remove resetting stream position --- src/ImageSharp/Image/Image{TColor}.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/ImageSharp/Image/Image{TColor}.cs b/src/ImageSharp/Image/Image{TColor}.cs index a64b515031..3ac1585a82 100644 --- a/src/ImageSharp/Image/Image{TColor}.cs +++ b/src/ImageSharp/Image/Image{TColor}.cs @@ -573,14 +573,7 @@ namespace ImageSharp Guard.NotNull(stream, nameof(stream)); Guard.NotNull(encoder, nameof(encoder)); - long startOfStream = stream.Position; encoder.Encode(this, stream, options); - - // Reset to the start of the stream. - if (stream.CanSeek) - { - stream.Position = startOfStream; - } } /// From 35f3be02c1d1e42acb6face5cf58fab49669a1e1 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 16 Mar 2017 02:21:16 +0100 Subject: [PATCH 06/83] Fixed Sandbox46 test execution --- tests/ImageSharp.Tests/TestFile.cs | 2 +- tests/ImageSharp.Tests/TestFont.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/TestFile.cs b/tests/ImageSharp.Tests/TestFile.cs index 939b1254e6..701025e871 100644 --- a/tests/ImageSharp.Tests/TestFile.cs +++ b/tests/ImageSharp.Tests/TestFile.cs @@ -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/" }; diff --git a/tests/ImageSharp.Tests/TestFont.cs b/tests/ImageSharp.Tests/TestFont.cs index e7ef63b891..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/" }; From 02db976b959740cce835b413a7d2887548e40d78 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 16 Mar 2017 02:33:50 +0100 Subject: [PATCH 07/83] Implemented PinnedBuffer.CreateClean() + format DrawText.cs --- .../Common/Memory/PinnedBuffer{T}.cs | 12 ++ .../Common/PinnedBufferTests.cs | 18 ++ .../ImageSharp.Tests/Drawing/Text/DrawText.cs | 185 ++++++++++-------- 3 files changed, 137 insertions(+), 78 deletions(-) diff --git a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs index 2d3d44dda8..d002e08fbd 100644 --- a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs +++ b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs @@ -109,6 +109,18 @@ namespace ImageSharp return buffer.Slice(); } + /// + /// Creates a clean instance of initializing it's elements with 'default(T)'. + /// + /// The desired count of elements. (Minimum size for ) + /// The instance + public static PinnedBuffer CreateClean(int count) + { + PinnedBuffer buffer = new PinnedBuffer(count); + buffer.Clear(); + return buffer; + } + /// /// Gets a to the beginning of the raw data of the buffer. /// diff --git a/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs index 26b529f6a5..d47bd5b94e 100644 --- a/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs +++ b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; + using System.Threading.Tasks; using Xunit; @@ -62,6 +63,23 @@ } } + [Fact] + public void CreateClean() + { + Parallel.For(0, 100, + i => + { + using (PinnedBuffer buffer = PinnedBuffer.CreateClean(42)) + { + for (int j = 0; j < buffer.Count; j++) + { + Assert.Equal(0, buffer.Array[j]); + buffer.Array[j] = 666; + } + } + }); + } + [Fact] public void Dispose() { diff --git a/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs b/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs index 2a2cb8a075..68db4d9a27 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,99 @@ 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); } } } From 1c841b731f15b51817af2dca45dbbdf5bc36c9eb Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 16 Mar 2017 02:38:44 +0100 Subject: [PATCH 08/83] Fixed a bug: When a processor "did not cover" the whole output image, there were memory garbage artifacts in the background. --- src/ImageSharp/Image/PixelAccessor{TColor}.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Image/PixelAccessor{TColor}.cs b/src/ImageSharp/Image/PixelAccessor{TColor}.cs index a106765652..926e9f0558 100644 --- a/src/ImageSharp/Image/PixelAccessor{TColor}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TColor}.cs @@ -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, PinnedBuffer.CreateClean(width * height)) { } From 6f4d716884e7584c3782047822d80c2f56e50e31 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Thu, 16 Mar 2017 08:32:47 +0000 Subject: [PATCH 09/83] use callbacks instead of overrides --- src/ImageSharp/Image/IImageCallbacks.cs | 46 +++++++++++++++++++ src/ImageSharp/Image/ImageBase{TColor}.cs | 12 ++++- src/ImageSharp/Image/Image{TColor}.cs | 35 +++++++------- .../Drawing/Paths/ProcessorWatchingImage.cs | 20 ++++---- .../Image/SaveWatchingImage.cs | 13 +++++- 5 files changed, 99 insertions(+), 27 deletions(-) create mode 100644 src/ImageSharp/Image/IImageCallbacks.cs diff --git a/src/ImageSharp/Image/IImageCallbacks.cs b/src/ImageSharp/Image/IImageCallbacks.cs new file mode 100644 index 0000000000..ed9b1f97cb --- /dev/null +++ b/src/ImageSharp/Image/IImageCallbacks.cs @@ -0,0 +1,46 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using ImageSharp.Processing; + + /// + /// Provides a set of methods that are called as part of the images lifetime/processes. + /// + internal interface IImageCallbacks + { + /// + /// Invoked before the image is saved. + /// + /// The color + /// The image + /// The destination stream + /// The encoder + /// The options + /// + /// return true if the processor should be applied otherwise false. + /// + bool OnSaving(ImageBase image, Stream stream, Formats.IImageEncoder encoder, IEncoderOptions options) + where TColor : struct, IPixel; + + /// + /// Invoked before the image is processed. + /// + /// The color + /// The image + /// The processor. + /// The rectangle. + /// + /// return true if the processor should be applied otherwise false. + /// + bool OnProcessing(ImageBase image, IImageProcessor processor, Rectangle rectangle) + where TColor : struct, IPixel; + } +} diff --git a/src/ImageSharp/Image/ImageBase{TColor}.cs b/src/ImageSharp/Image/ImageBase{TColor}.cs index 878ba09b39..f905123132 100644 --- a/src/ImageSharp/Image/ImageBase{TColor}.cs +++ b/src/ImageSharp/Image/ImageBase{TColor}.cs @@ -116,6 +116,11 @@ namespace ImageSharp /// public Configuration Configuration { get; private set; } + /// + /// Gets or sets the callbacks item which will be called during the lifetime of the image being processed. + /// + internal IImageCallbacks Callbacks { get; set; } + /// /// Applies the processor. /// @@ -123,7 +128,12 @@ namespace ImageSharp /// The rectangle. public virtual void ApplyProcessor(IImageProcessor processor, Rectangle rectangle) { - processor.Apply(this, rectangle); + // this will be null true or false, if its null or true then apply the processor + // thus is not false then apply the processors (allows for tests to save time and not actually run the prcessor is required) + if (this.Callbacks?.OnProcessing(this, processor, rectangle) != false) + { + processor.Apply(this, rectangle); + } } /// diff --git a/src/ImageSharp/Image/Image{TColor}.cs b/src/ImageSharp/Image/Image{TColor}.cs index 3ac1585a82..8e33715bb8 100644 --- a/src/ImageSharp/Image/Image{TColor}.cs +++ b/src/ImageSharp/Image/Image{TColor}.cs @@ -560,22 +560,6 @@ namespace ImageSharp return target; } - /// - /// Internally saves the image to the given stream using the given image encoder and options. - /// Can be used by overridden by tests to verify save opperations. - /// - /// The stream to save the image to. - /// The encoder to save the image with. - /// The options for the encoder. - /// Thrown if the stream or encoder is null. - internal virtual void SaveInternal(Stream stream, IImageEncoder encoder, IEncoderOptions options) - { - Guard.NotNull(stream, nameof(stream)); - Guard.NotNull(encoder, nameof(encoder)); - - encoder.Encode(this, stream, options); - } - /// /// Creates a new from this instance /// @@ -597,6 +581,25 @@ namespace ImageSharp base.Dispose(disposing); } + /// + /// Internally saves the image to the given stream using the given image encoder and options. + /// Can be used by overridden by tests to verify save opperations. + /// + /// The stream to save the image to. + /// The encoder to save the image with. + /// The options for the encoder. + /// Thrown if the stream or encoder is null. + private void SaveInternal(Stream stream, IImageEncoder encoder, IEncoderOptions options) + { + Guard.NotNull(stream, nameof(stream)); + Guard.NotNull(encoder, nameof(encoder)); + + if (this.Callbacks?.OnSaving(this, stream, encoder, options) != false) + { + encoder.Encode(this, stream, options); + } + } + /// /// Copies the properties from the other . /// diff --git a/tests/ImageSharp.Tests/Drawing/Paths/ProcessorWatchingImage.cs b/tests/ImageSharp.Tests/Drawing/Paths/ProcessorWatchingImage.cs index 3bb3b3e777..ff4432fd16 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/ProcessorWatchingImage.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/ProcessorWatchingImage.cs @@ -6,31 +6,35 @@ 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. /// /// - public class ProcessorWatchingImage : Image + public class ProcessorWatchingImage : Image, IImageCallbacks { public List ProcessorApplications { get; } = new List(); public ProcessorWatchingImage(int width, int height) : base(width, height, Configuration.CreateDefaultInstance()) { + this.Callbacks = this; } - public override void ApplyProcessor(IImageProcessor processor, Rectangle rectangle) + public bool OnSaving(ImageBase image, Stream stream, IImageEncoder encoder, IEncoderOptions options) where TColor : struct, IPixel { - ProcessorApplications.Add(new ProcessorDetails + return true; + } + + public bool OnProcessing(ImageBase image, IImageProcessor processor, Rectangle rectangle) where TColor : struct, IPixel + { + this.ProcessorApplications.Add(new ProcessorDetails { - processor = processor, + processor = (IImageProcessor)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 + return false;// do not really apply the processor to speed up testing } public struct ProcessorDetails diff --git a/tests/ImageSharp.Tests/Image/SaveWatchingImage.cs b/tests/ImageSharp.Tests/Image/SaveWatchingImage.cs index e9b45da696..0436146913 100644 --- a/tests/ImageSharp.Tests/Image/SaveWatchingImage.cs +++ b/tests/ImageSharp.Tests/Image/SaveWatchingImage.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Tests /// Watches but does not actually run the processors against the image. /// /// - public class SaveWatchingImage : Image + public class SaveWatchingImage : Image, IImageCallbacks { public List Saves { get; } = new List(); @@ -22,9 +22,11 @@ namespace ImageSharp.Tests { //switch out the file system for tests this.Configuration.FileSystem = fs ?? this.Configuration.FileSystem; + + this.Callbacks = this; } - internal override void SaveInternal(Stream stream, IImageEncoder encoder, IEncoderOptions options) + public bool OnSaving(ImageBase image, Stream stream, IImageEncoder encoder, IEncoderOptions options) where TColor : struct, IPixel { this.Saves.Add(new OperationDetails { @@ -32,6 +34,13 @@ namespace ImageSharp.Tests options = options, stream = stream }); + + return false; + } + + public bool OnProcessing(ImageBase image, IImageProcessor processor, Rectangle rectangle) where TColor : struct, IPixel + { + return false; } public struct OperationDetails From 7de5545da1b67b1a3784a7ad2256bf8054344e56 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Thu, 16 Mar 2017 08:36:24 +0000 Subject: [PATCH 10/83] fixed header --- src/ImageSharp/Image/IImageCallbacks.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Image/IImageCallbacks.cs b/src/ImageSharp/Image/IImageCallbacks.cs index ed9b1f97cb..93267de057 100644 --- a/src/ImageSharp/Image/IImageCallbacks.cs +++ b/src/ImageSharp/Image/IImageCallbacks.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // From 80d127fde6c31f574100f12c21de4f1cfb2b440c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 17 Mar 2017 00:04:27 +1100 Subject: [PATCH 11/83] Add indexed png encoder benchmark [skip ci] --- .../Image/EncodeIndexedPng.cs | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs diff --git a/tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs b/tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs new file mode 100644 index 0000000000..ba383873c2 --- /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 = new Image(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 From 58bd379394fcc4cb8e43c3332876b3e9c479bde1 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 17 Mar 2017 00:26:13 +1100 Subject: [PATCH 12/83] Make all processors internal --- src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs | 2 +- src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs | 2 +- src/ImageSharp.Drawing/Processors/FillProcessor.cs | 4 ++-- src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs | 2 +- src/ImageSharp/ImageProcessor.cs | 2 +- .../Processors/Binarization/BinaryThresholdProcessor.cs | 2 +- .../Binarization/ErrorDiffusionDitherProcessor.cs | 2 +- .../Processors/Binarization/OrderedDitherProcessor.cs | 2 +- .../Processors/ColorMatrix/BlackWhiteProcessor.cs | 2 +- .../ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs | 2 +- .../ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs | 2 +- .../ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs | 2 +- .../ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs | 2 +- .../ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs | 2 +- .../ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs | 2 +- .../ColorMatrix/ColorBlindness/TritanomalyProcessor.cs | 2 +- .../ColorMatrix/ColorBlindness/TritanopiaProcessor.cs | 2 +- .../{ColorMatrixFilter.cs => ColorMatrixProcessor.cs} | 2 +- .../Processors/ColorMatrix/GrayscaleBt601Processor.cs | 6 +++--- .../Processors/ColorMatrix/GrayscaleBt709Processor.cs | 6 +++--- .../Processing/Processors/ColorMatrix/HueProcessor.cs | 2 +- .../Processing/Processors/ColorMatrix/IColorMatrixFilter.cs | 2 +- .../Processors/ColorMatrix/KodachromeProcessor.cs | 2 +- .../Processing/Processors/ColorMatrix/LomographProcessor.cs | 2 +- .../Processing/Processors/ColorMatrix/PolaroidProcessor.cs | 2 +- .../Processors/ColorMatrix/SaturationProcessor.cs | 2 +- .../Processing/Processors/ColorMatrix/SepiaProcessor.cs | 2 +- .../Processing/Processors/Convolution/BoxBlurProcessor.cs | 2 +- .../Processors/Convolution/Convolution2DProcessor.cs | 2 +- .../Processors/Convolution/Convolution2PassProcessor.cs | 2 +- .../Processors/Convolution/ConvolutionProcessor.cs | 2 +- .../Convolution/EdgeDetection/EdgeDetector2DProcessor.cs | 2 +- .../EdgeDetection/EdgeDetectorCompassProcessor.cs | 2 +- .../Convolution/EdgeDetection/EdgeDetectorProcessor.cs | 2 +- .../Convolution/EdgeDetection/KayyaliProcessor.cs | 2 +- .../Processors/Convolution/EdgeDetection/KirschProcessor.cs | 2 +- .../Convolution/EdgeDetection/Laplacian3X3Processor.cs | 2 +- .../Convolution/EdgeDetection/Laplacian5X5Processor.cs | 2 +- .../EdgeDetection/LaplacianOfGaussianProcessor.cs | 2 +- .../Convolution/EdgeDetection/PrewittProcessor.cs | 2 +- .../Convolution/EdgeDetection/RobertsCrossProcessor.cs | 2 +- .../Convolution/EdgeDetection/RobinsonProcessor.cs | 2 +- .../Processors/Convolution/EdgeDetection/ScharrProcessor.cs | 2 +- .../Processors/Convolution/EdgeDetection/SobelProcessor.cs | 2 +- .../Processors/Convolution/GaussianBlurProcessor.cs | 2 +- .../Processors/Convolution/GaussianSharpenProcessor.cs | 2 +- .../Processing/Processors/Effects/AlphaProcessor.cs | 2 +- .../Processors/Effects/BackgroundColorProcessor.cs | 2 +- .../Processing/Processors/Effects/BrightnessProcessor.cs | 2 +- .../Processing/Processors/Effects/ContrastProcessor.cs | 2 +- .../Processing/Processors/Effects/InvertProcessor.cs | 2 +- .../Processing/Processors/Effects/OilPaintingProcessor.cs | 2 +- .../Processing/Processors/Effects/PixelateProcessor.cs | 2 +- .../Processing/Processors/Overlays/GlowProcessor.cs | 2 +- .../Processing/Processors/Overlays/VignetteProcessor.cs | 2 +- .../Processors/Transforms/CompandingResizeProcessor.cs | 2 +- .../Processing/Processors/Transforms/CropProcessor.cs | 2 +- .../Processors/Transforms/EntropyCropProcessor.cs | 2 +- .../Processing/Processors/Transforms/FlipProcessor.cs | 2 +- .../Processing/Processors/Transforms/Matrix3x2Processor.cs | 2 +- .../Processors/Transforms/ResamplingWeightedProcessor.cs | 2 +- .../Processing/Processors/Transforms/ResizeProcessor.cs | 2 +- .../Processing/Processors/Transforms/RotateProcessor.cs | 2 +- .../Processing/Processors/Transforms/SkewProcessor.cs | 2 +- 64 files changed, 69 insertions(+), 69 deletions(-) rename src/ImageSharp/Processing/Processors/ColorMatrix/{ColorMatrixFilter.cs => ColorMatrixProcessor.cs} (96%) 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..67b4aab5f7 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; 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/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..77608e02be 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 { /// 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..3568afe418 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 { /// 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..c5302bad03 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 { /// 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..bb54c1f98b 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 { /// diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs index f1872d0b81..6eda3e42a0 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 { /// diff --git a/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs index 2190254f0d..f5314d448d 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// This version will expand and compress the image to and from a linear color space during processing. /// /// The pixel format. - public class CompandingResizeProcessor : ResamplingWeightedProcessor + internal class CompandingResizeProcessor : ResamplingWeightedProcessor where TColor : struct, IPixel { /// 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.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs index 25a7a4efd6..2d6de41545 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing.Processors /// Adapted from /// /// The pixel format. - public abstract class ResamplingWeightedProcessor : ImageProcessor + internal abstract class ResamplingWeightedProcessor : ImageProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs index 9ec804aa4f..a43745a057 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs @@ -16,7 +16,7 @@ namespace ImageSharp.Processing.Processors /// 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 { /// diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs index 22fbb895c1..eb206380c1 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 { /// 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 { /// From 56be333c0ad4de238b9d682933c658dc424425a9 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 17 Mar 2017 00:36:25 +1100 Subject: [PATCH 13/83] Internalize memory objects --- .../Common/Memory/PixelDataPool{T}.cs | 2 +- src/ImageSharp/Image/PixelAccessor{TColor}.cs | 68 +++++++++---------- src/ImageSharp/Image/PixelArea{TColor}.cs | 4 +- 3 files changed, 36 insertions(+), 38 deletions(-) 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/Image/PixelAccessor{TColor}.cs b/src/ImageSharp/Image/PixelAccessor{TColor}.cs index 926e9f0558..25e232cf86 100644 --- a/src/ImageSharp/Image/PixelAccessor{TColor}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TColor}.cs @@ -148,6 +148,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 +188,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 +204,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 +221,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,37 +238,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'. /// diff --git a/src/ImageSharp/Image/PixelArea{TColor}.cs b/src/ImageSharp/Image/PixelArea{TColor}.cs index be6debba2f..8f2fa5b7a1 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 { /// From c9ef65c6b4d637eb443c51ff62e7a51125a2cead Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 17 Mar 2017 00:48:53 +1100 Subject: [PATCH 14/83] Bump version number --- src/ImageSharp.Drawing/ImageSharp.Drawing.csproj | 2 +- src/ImageSharp/ImageSharp.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj index cc1c526ae3..f690dac8d5 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-alpha4 James Jackson-South and contributors netstandard1.1 true diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index de3e764734..63cb081c33 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-alpha4 James Jackson-South and contributors netstandard1.3;netstandard1.1 true From d975d3a06bfe72a87104c6b370baed125ca33a45 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 17 Mar 2017 00:49:49 +1100 Subject: [PATCH 15/83] Update readme --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index 967bccf8aa..383cc11f87 100644 --- a/README.md +++ b/README.md @@ -93,12 +93,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 From 24caf763d760dbd396518e7cb6a26dedc78ff480 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 17 Mar 2017 10:25:37 +1100 Subject: [PATCH 16/83] Make Fast2DArray internal --- .../Common/Memory/Fast2DArray{T}.cs | 2 +- .../Dithering/ErrorDiffusion/ErrorDiffuser.cs | 23 +++++------ .../ErrorDiffusion/IErrorDiffuser.cs | 5 --- src/ImageSharp/Dithering/Ordered/Bayer.cs | 17 +++------ .../Dithering/Ordered/IOrderedDither.cs | 9 +---- src/ImageSharp/Dithering/Ordered/Ordered.cs | 17 +++------ .../Dithering/Ordered/OrderedDither4x4.cs | 38 +++++++++++++++++++ 7 files changed, 64 insertions(+), 47 deletions(-) create mode 100644 src/ImageSharp/Dithering/Ordered/OrderedDither4x4.cs 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/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 From 53ca3d5de1cfe0609fda1a08ea47378fef52fc14 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 17 Mar 2017 17:22:36 +1100 Subject: [PATCH 17/83] Make QuantizePixel non-inheritable --- src/ImageSharp/Quantizers/IQuantizer.cs | 2 - .../Quantizers/Octree/OctreeQuantizer.cs | 94 +++++++++++++++++-- src/ImageSharp/Quantizers/Octree/Quantizer.cs | 55 ++--------- .../Quantizers/Palette/PaletteQuantizer.cs | 67 ++++++++++++- 4 files changed, 158 insertions(+), 60 deletions(-) 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..0e6540d426 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. @@ -215,7 +178,7 @@ namespace ImageSharp.Quantizers } // 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 From 0a81f28c40f06dad9c9e42e610620ab2710f6524 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 17 Mar 2017 14:34:54 +0000 Subject: [PATCH 18/83] implement real sub-pixel rendering --- .../Brushes/ImageBrush{TColor}.cs | 44 ++- .../Brushes/PatternBrush{TColor}.cs | 99 +++--- .../Brushes/Processors/BrushApplicator.cs | 53 ++- .../Brushes/RecolorBrush{TColor}.cs | 48 ++- .../Brushes/SolidBrush{TColor}.cs | 37 +- src/ImageSharp.Drawing/GraphicsOptions.cs | 6 + src/ImageSharp.Drawing/Paths/ShapeRegion.cs | 5 +- .../Processors/FillRegionProcessor.cs | 335 ++++-------------- src/ImageSharp.Drawing/Region.cs | 6 +- .../Text/TextGraphicsOptions.cs | 16 +- .../Common/Memory/BufferPointer{T}.cs | 14 + .../Drawing/SolidBezierTests.cs | 13 +- .../Drawing/SolidComplexPolygonTests.cs | 38 +- .../Drawing/SolidPolygonTests.cs | 26 +- 14 files changed, 336 insertions(+), 404 deletions(-) diff --git a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs index 732cd4f8be..400c85bc70 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,7 +71,11 @@ 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; @@ -87,7 +91,7 @@ namespace ImageSharp.Drawing.Brushes /// /// The color /// - public override TColor this[int x, int y] + internal override TColor this[int x, int y] { get { @@ -95,10 +99,10 @@ namespace ImageSharp.Drawing.Brushes // 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,34 @@ namespace ImageSharp.Drawing.Brushes { this.source.Dispose(); } + + internal override void Apply(float[] scanline, int scanlineWidth, int offset, int x, int y) + { + using (PinnedBuffer buffer = new PinnedBuffer(scanline)) + { + var slice = buffer.Slice(offset); + + for (var 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..8da8c7f99c 100644 --- a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs @@ -47,12 +47,8 @@ namespace ImageSharp.Drawing.Brushes /// /// The pattern. /// - private readonly TColor[][] pattern; - - /// - /// The stride width. - /// - private readonly int stride; + private readonly Fast2DArray pattern; + private readonly Fast2DArray patternVector; /// /// Initializes a new instance of the class. @@ -60,26 +56,23 @@ namespace ImageSharp.Drawing.Brushes /// Color of the fore. /// Color of the back. /// The pattern. - public PatternBrush(TColor foreColor, TColor backColor, bool[,] pattern) + public 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 +84,12 @@ namespace ImageSharp.Drawing.Brushes internal PatternBrush(PatternBrush brush) { this.pattern = brush.pattern; - this.stride = brush.stride; } /// public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region) { - return new PatternBrushApplicator(this.pattern, this.stride); + return new PatternBrushApplicator(sourcePixels, this.pattern, this.patternVector); } /// @@ -105,31 +97,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 +124,14 @@ 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.Height; + y = y % this.pattern.Width; - return this.pattern[x][y]; + return this.pattern[x, y]; } } @@ -156,6 +140,33 @@ namespace ImageSharp.Drawing.Brushes { // noop } + + internal override void Apply(float[] scanline, int scanlineWidth, int offset, int x, int y) + { + using (PinnedBuffer buffer = new PinnedBuffer(scanline)) + { + var slice = buffer.Slice(offset); + + for (var 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.patternVector[targetX % this.pattern.Height, targetX % this.pattern.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..49c405abf1 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,65 @@ 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; + } + + /// + /// 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 burshed 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. + internal virtual void Apply(float[] scanline, int scanlineWidth, int offset, int x, int y) + { + using (PinnedBuffer buffer = new PinnedBuffer(scanline)) + { + var slice = buffer.Slice(offset); + + for (var 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 db8f3705e3..158f0309f8 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,12 +109,12 @@ 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) @@ -140,6 +135,43 @@ namespace ImageSharp.Drawing.Brushes public override void Dispose() { } + + internal override void Apply(float[] scanline, int scanlineWidth, int offset, int x, int y) + { + using (PinnedBuffer buffer = new PinnedBuffer(scanline)) + { + var slice = buffer.Slice(offset); + + for (var 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..67d4844358 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,40 @@ 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[] scanline, int scanlineWidth, int offset, int x, int y) + { + using (PinnedBuffer buffer = new PinnedBuffer(scanline)) + { + var slice = buffer.Slice(offset); + + for (var 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/Paths/ShapeRegion.cs b/src/ImageSharp.Drawing/Paths/ShapeRegion.cs index b02c5c2e5b..9a350857cf 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,7 +40,7 @@ namespace ImageSharp.Drawing public override Rectangle Bounds { get; } /// - public override int ScanX(int x, float[] buffer, int length, int offset) + public override int ScanX(float 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); @@ -62,7 +63,7 @@ namespace ImageSharp.Drawing } /// - public override int ScanY(int y, float[] buffer, int length, int offset) + public override int ScanY(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/Processors/FillRegionProcessor.cs b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs index 67b4aab5f7..81de21b5f1 100644 --- a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs @@ -57,315 +57,116 @@ 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); 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 (var 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) - { - // 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 (scanlineDirty) { - 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; - } - - 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; - } + scanline[x] = 0; } - 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.ScanY(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) - { - if (y < (nextPoint - DrawPadding) && y > (lastPoint + DrawPadding)) - { - if (nextPoint == right) - { - // we are in the ends run skip it - y = maxY; - continue; - } + // points will be paired up + float scanStart = buffer[point] - minX; + float scanEnd = buffer[point + 1] - minX; + int startX = (int)Math.Floor(scanStart); + int endX = (int)Math.Floor(scanEnd); - // lets just jump forward - y = (int)Math.Floor(nextPoint) - DrawPadding; - } - } - else + for (float x = scanStart; x < startX + 1; x += subpixelFraction) { - if (y < nextPoint - DrawPadding) - { - if (nextPoint == right) - { - // we are in the ends run skip it - y = maxY; - continue; - } - - // lets just jump forward - y = (int)Math.Floor(nextPoint); - } + scanline[startX] += subpixelFractionPoint; + scanlineDirty = true; } - bool onCorner = false; - - if (y >= nextPoint) + for (float x = endX; x < scanEnd; x += subpixelFraction) { - 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]; - } - } - } + scanline[endX] += subpixelFractionPoint; + scanlineDirty = true; + } - isInside ^= true; + for (int x = startX + 1; x < endX; x++) + { + scanline[x] += subpixelFraction; + scanlineDirty = 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..f62fb0b032 100644 --- a/src/ImageSharp.Drawing/Region.cs +++ b/src/ImageSharp.Drawing/Region.cs @@ -19,7 +19,7 @@ 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 either or . /// public abstract Rectangle Bounds { get; } @@ -31,7 +31,7 @@ namespace ImageSharp.Drawing /// The length. /// The offset. /// The number of intersections found. - public abstract int ScanX(int x, float[] buffer, int length, int offset); + public abstract int ScanX(float x, float[] buffer, int length, int offset); /// /// Scans the Y axis for intersections. @@ -41,6 +41,6 @@ namespace ImageSharp.Drawing /// The length. /// The offset. /// The number of intersections found. - public abstract int ScanY(int y, float[] buffer, int length, int offset); + public abstract int ScanY(float y, float[] buffer, int length, int offset); } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs b/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs index e707ef5e50..253bb2aaca 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. /// @@ -39,6 +44,7 @@ namespace ImageSharp.Drawing this.Antialias = enableAntialiasing; this.ApplyKerning = true; this.TabWidth = 4; + this.AntialiasSubpixelDepth = 16; } /// @@ -50,7 +56,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 +71,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/Common/Memory/BufferPointer{T}.cs b/src/ImageSharp/Common/Memory/BufferPointer{T}.cs index 441f6b8ce0..167731e369 100644 --- a/src/ImageSharp/Common/Memory/BufferPointer{T}.cs +++ b/src/ImageSharp/Common/Memory/BufferPointer{T}.cs @@ -73,6 +73,20 @@ namespace ImageSharp /// public IntPtr PointerAtOffset { get; private set; } + /// + /// Gets or sets the pixel at the specified position. + /// + /// The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image. + /// The at the specified position. + public T this[int x] + { + get + { + void* ptr = ((byte*)this.PointerAtOffset) + (x * Unsafe.SizeOf()); + return Unsafe.Read(ptr); + } + } + /// /// Convertes instance to a raw 'void*' pointer /// 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 ce13d9f0f6..4ff250a934 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs @@ -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 54c4af38b1..6f9b31e367 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs @@ -38,13 +38,7 @@ 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.NotEqual(Color.HotPink, sourcePixels[2, 2]); + Assert.Equal(Color.HotPink, sourcePixels[81, 145]); } } } @@ -129,12 +123,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 +175,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]); } } } From 97f5f3e4083f0b221e63966d0927be902960111a Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 17 Mar 2017 14:54:38 +0000 Subject: [PATCH 19/83] get x=>width y=>height the correct way around. --- src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs index 8da8c7f99c..a143d6f311 100644 --- a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs @@ -128,8 +128,8 @@ namespace ImageSharp.Drawing.Brushes { get { - x = x % this.pattern.Height; - y = y % this.pattern.Width; + x = x % this.pattern.Width; + y = y % this.pattern.Height; return this.pattern[x, y]; } From 44d2736b8fe59d15c37511bc7a86640c02e96cbc Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 17 Mar 2017 15:28:19 +0000 Subject: [PATCH 20/83] fix pattern brushes --- .../Brushes/Brushes{TColor}.cs | 40 ++++++++++--------- .../Brushes/PatternBrush{TColor}.cs | 29 ++++++++------ .../Drawing/FillPatternTests.cs | 37 +++++++++-------- 3 files changed, 56 insertions(+), 50 deletions(-) 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/PatternBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs index a143d6f311..9ebaf811a2 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 @@ -56,7 +47,18 @@ namespace ImageSharp.Drawing.Brushes /// Color of the fore. /// Color of the back. /// The pattern. - public PatternBrush(TColor foreColor, TColor backColor, Fast2DArray pattern) + public PatternBrush(TColor foreColor, TColor backColor, bool[,] pattern) + : this(foreColor, backColor, new Fast2DArray(pattern)) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Color of the fore. + /// Color of the back. + /// The pattern. + internal PatternBrush(TColor foreColor, TColor backColor, Fast2DArray pattern) { Vector4 foreColorVector = foreColor.ToVector4(); Vector4 backColorVector = backColor.ToVector4(); @@ -131,7 +133,8 @@ namespace ImageSharp.Drawing.Brushes 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,7 +159,9 @@ namespace ImageSharp.Drawing.Brushes if (opacity > Constants.Epsilon) { Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4(); - Vector4 sourceVector = this.patternVector[targetX % this.pattern.Height, targetX % this.pattern.Width]; + + // 2d array index at row/column + Vector4 sourceVector = this.patternVector[targetY % this.pattern.Height, targetX % this.pattern.Width]; Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); diff --git a/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs b/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs index d3c1877abd..56066a7aa5 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); + var 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} }); } - - } } From d757809bcf4d1b928a93abce0ce576e4412d0e43 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 17 Mar 2017 17:19:12 +0000 Subject: [PATCH 21/83] fix comments --- src/ImageSharp/Common/Memory/BufferPointer{T}.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Common/Memory/BufferPointer{T}.cs b/src/ImageSharp/Common/Memory/BufferPointer{T}.cs index 167731e369..0673efae06 100644 --- a/src/ImageSharp/Common/Memory/BufferPointer{T}.cs +++ b/src/ImageSharp/Common/Memory/BufferPointer{T}.cs @@ -73,16 +73,16 @@ namespace ImageSharp /// public IntPtr PointerAtOffset { get; private set; } - /// - /// Gets or sets the pixel at the specified position. + /// + /// Gets the element at the specified position. /// - /// The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image. - /// The at the specified position. - public T this[int x] + /// The index from the start of this Pointer to the required element. + /// The at the specified position. + public T this[int index] { get { - void* ptr = ((byte*)this.PointerAtOffset) + (x * Unsafe.SizeOf()); + byte* ptr = ((byte*)this.PointerAtOffset) + BufferPointer.SizeOf(index); return Unsafe.Read(ptr); } } From 0e75d2db995fa1382f9ad7c2657298698257183d Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 17 Mar 2017 18:58:27 +0000 Subject: [PATCH 22/83] fixed comment --- src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs index 49c405abf1..87cf949e8f 100644 --- a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs +++ b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs @@ -27,7 +27,7 @@ namespace ImageSharp.Drawing.Processors } /// - /// The destinaion + /// Gets the destinaion /// protected PixelAccessor Target { get; } From 34b09eb0994a9feee1adaff08f865593ffa87a3e Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 17 Mar 2017 19:37:13 +0000 Subject: [PATCH 23/83] code cleanup --- .../Brushes/ImageBrush{TColor}.cs | 4 +- .../Brushes/PatternBrush{TColor}.cs | 4 +- .../Brushes/Processors/BrushApplicator.cs | 4 +- .../Brushes/RecolorBrush{TColor}.cs | 4 +- .../Brushes/SolidBrush{TColor}.cs | 4 +- src/ImageSharp.Drawing/Paths/ShapeRegion.cs | 25 +---------- .../Processors/FillRegionProcessor.cs | 4 +- src/ImageSharp.Drawing/Region.cs | 16 ++----- .../Drawing/FillPatternTests.cs | 2 +- .../Drawing/Paths/ShapeRegionTests.cs | 43 +------------------ 10 files changed, 19 insertions(+), 91 deletions(-) diff --git a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs index 400c85bc70..f3ea81cf61 100644 --- a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs @@ -116,9 +116,9 @@ namespace ImageSharp.Drawing.Brushes { using (PinnedBuffer buffer = new PinnedBuffer(scanline)) { - var slice = buffer.Slice(offset); + BufferPointer slice = buffer.Slice(offset); - for (var xPos = 0; xPos < scanlineWidth; xPos++) + for (int xPos = 0; xPos < scanlineWidth; xPos++) { int targetX = xPos + x; int targetY = y; diff --git a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs index 9ebaf811a2..55152d234f 100644 --- a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs @@ -148,9 +148,9 @@ namespace ImageSharp.Drawing.Brushes { using (PinnedBuffer buffer = new PinnedBuffer(scanline)) { - var slice = buffer.Slice(offset); + BufferPointer slice = buffer.Slice(offset); - for (var xPos = 0; xPos < scanlineWidth; xPos++) + for (int xPos = 0; xPos < scanlineWidth; xPos++) { int targetX = xPos + x; int targetY = y; diff --git a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs index 87cf949e8f..679d871700 100644 --- a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs +++ b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs @@ -54,9 +54,9 @@ namespace ImageSharp.Drawing.Processors { using (PinnedBuffer buffer = new PinnedBuffer(scanline)) { - var slice = buffer.Slice(offset); + BufferPointer slice = buffer.Slice(offset); - for (var xPos = 0; xPos < scanlineWidth; xPos++) + for (int xPos = 0; xPos < scanlineWidth; xPos++) { int targetX = xPos + x; int targetY = y; diff --git a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs index 158f0309f8..92e5191610 100644 --- a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs @@ -140,9 +140,9 @@ namespace ImageSharp.Drawing.Brushes { using (PinnedBuffer buffer = new PinnedBuffer(scanline)) { - var slice = buffer.Slice(offset); + BufferPointer slice = buffer.Slice(offset); - for (var xPos = 0; xPos < scanlineWidth; xPos++) + for (int xPos = 0; xPos < scanlineWidth; xPos++) { int targetX = xPos + x; int targetY = y; diff --git a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs index 67d4844358..bdac7fdc71 100644 --- a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs @@ -88,9 +88,9 @@ namespace ImageSharp.Drawing.Brushes { using (PinnedBuffer buffer = new PinnedBuffer(scanline)) { - var slice = buffer.Slice(offset); + BufferPointer slice = buffer.Slice(offset); - for (var xPos = 0; xPos < scanlineWidth; xPos++) + for (int xPos = 0; xPos < scanlineWidth; xPos++) { int targetX = xPos + x; int targetY = y; diff --git a/src/ImageSharp.Drawing/Paths/ShapeRegion.cs b/src/ImageSharp.Drawing/Paths/ShapeRegion.cs index 9a350857cf..0868945318 100644 --- a/src/ImageSharp.Drawing/Paths/ShapeRegion.cs +++ b/src/ImageSharp.Drawing/Paths/ShapeRegion.cs @@ -40,30 +40,7 @@ namespace ImageSharp.Drawing public override Rectangle Bounds { get; } /// - public override int ScanX(float 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(float 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/Processors/FillRegionProcessor.cs b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs index 81de21b5f1..66e1f4380c 100644 --- a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs @@ -88,7 +88,7 @@ namespace ImageSharp.Drawing.Processors try { bool scanlineDirty = true; - for (var y = minY; y < maxY; y++) + for (int y = minY; y < maxY; y++) { if (scanlineDirty) { @@ -105,7 +105,7 @@ namespace ImageSharp.Drawing.Processors float subpixelFractionPoint = subpixelFraction / subpixelCount; for (float subPixel = (float)y; subPixel < y + 1; subPixel += subpixelFraction) { - int pointsFound = region.ScanY(subPixel, buffer, maxIntersections, 0); + int pointsFound = region.Scan(subPixel, buffer, maxIntersections, 0); if (pointsFound == 0) { // nothing on this line skip diff --git a/src/ImageSharp.Drawing/Region.cs b/src/ImageSharp.Drawing/Region.cs index f62fb0b032..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(float 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(float 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/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs b/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs index 56066a7aa5..8162bc5319 100644 --- a/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs @@ -33,7 +33,7 @@ namespace ImageSharp.Tests.Drawing { // lets pick random spots to start checking Random r = new Random(); - var expectedPatternFast = new Fast2DArray(expectedPattern); + Fast2DArray expectedPatternFast = new Fast2DArray(expectedPattern); int xStride = expectedPatternFast.Width; int yStride = expectedPatternFast.Height; int offsetX = r.Next(image.Width / xStride) * xStride; 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); } From 56b0a9f8e1016cf2c57fa7cba5d736bd708b5692 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 17 Mar 2017 20:37:15 +0000 Subject: [PATCH 24/83] Add test coving subpixel counter --- .../Drawing/FillRegionProcessorTests.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs diff --git a/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs b/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs new file mode 100644 index 0000000000..db9686f3d2 --- /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) + { + var 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)); + } + } +} From af0d8cd8c53a9d8186a4d77e07c46e0483585a6d Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 17 Mar 2017 21:30:29 +0000 Subject: [PATCH 25/83] TextGraphicsOptions Tests --- .../Drawing/Text/TextGraphicsOptionsTests.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 tests/ImageSharp.Tests/Drawing/Text/TextGraphicsOptionsTests.cs 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 From b2fb41fab509c1154947f48a15ca09352732ab70 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 18 Mar 2017 00:01:31 +0100 Subject: [PATCH 26/83] BufferPointer became BufferSpan --- src/ImageSharp/Colors/Color.BulkOperations.cs | 32 +-- .../BulkPixelOperations{TColor}.cs | 84 +++---- .../Common/Memory/BufferPointer{T}.cs | 143 ------------ .../{BufferPointer.cs => BufferSpan.cs} | 28 +-- src/ImageSharp/Common/Memory/BufferSpan{T}.cs | 209 ++++++++++++++++++ .../Common/Memory/PinnedBuffer{T}.cs | 48 ++-- src/ImageSharp/Image/PixelAccessor{TColor}.cs | 38 ++-- src/ImageSharp/Image/PixelArea{TColor}.cs | 6 +- .../Colors/BulkPixelOperationsTests.cs | 8 +- ...fferPointerTests.cs => BufferSpanTests.cs} | 184 +++++++++------ .../Common/PinnedBufferTests.cs | 10 +- 11 files changed, 455 insertions(+), 335 deletions(-) delete mode 100644 src/ImageSharp/Common/Memory/BufferPointer{T}.cs rename src/ImageSharp/Common/Memory/{BufferPointer.cs => BufferSpan.cs} (81%) create mode 100644 src/ImageSharp/Common/Memory/BufferSpan{T}.cs rename tests/ImageSharp.Tests/Common/{BufferPointerTests.cs => BufferSpanTests.cs} (66%) diff --git a/src/ImageSharp/Colors/Color.BulkOperations.cs b/src/ImageSharp/Colors/Color.BulkOperations.cs index 5c040e04c5..039fafced6 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,8 +38,8 @@ namespace ImageSharp /// /// internal static unsafe void ToVector4SimdAligned( - BufferPointer sourceColors, - BufferPointer destVectors, + BufferSpan sourceColors, + BufferSpan destVectors, int count) { int vecSize = Vector.Count; @@ -85,12 +85,12 @@ 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) { @@ -117,7 +117,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 +132,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 +149,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 +176,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 +193,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 +208,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/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/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..0507173e90 --- /dev/null +++ b/src/ImageSharp/Common/Memory/BufferSpan{T}.cs @@ -0,0 +1,209 @@ +// +// 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; } + + /// + /// 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 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, BufferSpan.USizeOf(count)); + } + else + { + System.Array.Clear(this.Array, this.Start, count); + } + } + + [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/PinnedBuffer{T}.cs b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs index d002e08fbd..7378a8d640 100644 --- a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs +++ b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs @@ -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 count of "relevant" elements in 'array'. /// The array to pin. - public PinnedBuffer(int count, T[] array) + public PinnedBuffer(int length, T[] array) { - 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,11 +100,11 @@ namespace ImageSharp public IntPtr Pointer { get; private set; } /// - /// Converts to an . + /// Converts to an . /// /// The to convert. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator BufferPointer(PinnedBuffer buffer) + public static implicit operator BufferSpan(PinnedBuffer buffer) { return buffer.Slice(); } @@ -122,24 +122,24 @@ namespace ImageSharp } /// - /// Gets a to the beginning of the raw data of the buffer. + /// Gets a to the beginning of the raw data of the buffer. /// - /// The + /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe BufferPointer Slice() + public unsafe BufferSpan Slice() { - return new BufferPointer(this.Array, (void*)this.Pointer); + return new BufferSpan(this.Array, (void*)this.Pointer); } /// - /// Gets a to an offseted position inside the buffer. + /// Gets a to an offseted position inside the buffer. /// /// The offset - /// The + /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe BufferPointer Slice(int offset) + public unsafe BufferSpan Slice(int offset) { - return new BufferPointer(this.Array, (void*)this.Pointer, offset); + return new BufferSpan(this.Array, (void*)this.Pointer, offset); } /// @@ -163,7 +163,7 @@ namespace ImageSharp this.isPoolingOwner = false; this.Array = null; - this.Count = 0; + this.Length = 0; GC.SuppressFinalize(this); } @@ -190,12 +190,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); + this.Slice().Clear(this.Length); } /// diff --git a/src/ImageSharp/Image/PixelAccessor{TColor}.cs b/src/ImageSharp/Image/PixelAccessor{TColor}.cs index 25e232cf86..d22deec742 100644 --- a/src/ImageSharp/Image/PixelAccessor{TColor}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TColor}.cs @@ -239,12 +239,12 @@ namespace ImageSharp } /// - /// Gets a to the row 'y' beginning from the pixel at 'x'. + /// 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) + /// The + internal BufferSpan GetRowPointer(int x, int y) { return this.pixelBuffer.Slice((y * this.Width) + x); } @@ -288,8 +288,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.GetRowPointer(y); + BufferSpan destination = this.GetRowPointer(targetX, targetY + y); Operations.PackFromZyxBytes(source, destination, width); } @@ -308,8 +308,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.GetRowPointer(y); + BufferSpan destination = this.GetRowPointer(targetX, targetY + y); Operations.PackFromZyxwBytes(source, destination, width); } @@ -328,8 +328,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.GetRowPointer(y); + BufferSpan destination = this.GetRowPointer(targetX, targetY + y); Operations.PackFromXyzBytes(source, destination, width); } @@ -348,8 +348,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.GetRowPointer(y); + BufferSpan destination = this.GetRowPointer(targetX, targetY + y); Operations.PackFromXyzwBytes(source, destination, width); } } @@ -367,8 +367,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.GetRowPointer(sourceX, sourceY + y); + BufferSpan destination = area.GetRowPointer(y); Operations.ToZyxBytes(source, destination, width); } } @@ -386,8 +386,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.GetRowPointer(sourceX, sourceY + y); + BufferSpan destination = area.GetRowPointer(y); Operations.ToZyxwBytes(source, destination, width); } } @@ -405,8 +405,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.GetRowPointer(sourceX, sourceY + y); + BufferSpan destination = area.GetRowPointer(y); Operations.ToXyzBytes(source, destination, width); } } @@ -424,8 +424,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.GetRowPointer(sourceX, sourceY + y); + BufferSpan destination = area.GetRowPointer(y); Operations.ToXyzwBytes(source, destination, width); } } diff --git a/src/ImageSharp/Image/PixelArea{TColor}.cs b/src/ImageSharp/Image/PixelArea{TColor}.cs index 8f2fa5b7a1..0a3c5710ce 100644 --- a/src/ImageSharp/Image/PixelArea{TColor}.cs +++ b/src/ImageSharp/Image/PixelArea{TColor}.cs @@ -202,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 GetRowPointer(int y) { return this.byteBuffer.Slice(y * this.RowStride); } diff --git a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs index fa950c4cf2..141c9d888d 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.Slice(); + public BufferSpan ActualDest => this.ActualDestBuffer.Slice(); 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,7 +364,7 @@ namespace ImageSharp.Tests.Colors internal static void TestOperation( TSource[] source, TDest[] expected, - Action, BufferPointer> action) + Action, BufferSpan> action) where TSource : struct where TDest : struct { diff --git a/tests/ImageSharp.Tests/Common/BufferPointerTests.cs b/tests/ImageSharp.Tests/Common/BufferSpanTests.cs similarity index 66% rename from tests/ImageSharp.Tests/Common/BufferPointerTests.cs rename to tests/ImageSharp.Tests/Common/BufferSpanTests.cs index c82b63f115..f524bdb437 100644 --- a/tests/ImageSharp.Tests/Common/BufferPointerTests.cs +++ b/tests/ImageSharp.Tests/Common/BufferSpanTests.cs @@ -7,7 +7,7 @@ namespace ImageSharp.Tests.Common using Xunit; - public unsafe class BufferPointerTests + public unsafe class BufferSpanTests { public struct Foo { @@ -70,68 +70,122 @@ namespace ImageSharp.Tests.Common using (PinnedBuffer colorBuf = new PinnedBuffer(fooz)) { - BufferPointer orig = colorBuf.Slice(1); - BufferPointer asBytes = (BufferPointer < byte > )orig; + BufferSpan orig = colorBuf.Slice(1); + BufferSpan asBytes = (BufferSpan < byte > )orig; - Assert.Equal(asBytes.Offset, sizeof(Foo)); + Assert.Equal(asBytes.Start, sizeof(Foo)); Assert.Equal(orig.PointerAtOffset, asBytes.PointerAtOffset); } } - - [Fact] - public void ConstructWithoutOffset() + + public class Construct { - Foo[] array = Foo.CreateArray(3); - fixed (Foo* p = array) + [Fact] + public void Basic() { - // Act: - BufferPointer ap = new BufferPointer(array, p); + Foo[] array = Foo.CreateArray(3); + fixed (Foo* p = array) + { + // Act: + BufferSpan span = new BufferSpan(array, p); - // Assert: - Assert.Equal(array, ap.Array); - Assert.Equal((IntPtr)p, ap.PointerAtOffset); + // Assert: + Assert.Equal(array, span.Array); + Assert.Equal((IntPtr)p, span.PointerAtOffset); + Assert.Equal(3, span.Length); + } } - } - [Fact] - public void ConstructWithOffset() - { - Foo[] array = Foo.CreateArray(3); - int offset = 2; - fixed (Foo* p = array) + [Fact] + public void WithStart() { - // Act: - BufferPointer ap = new BufferPointer(array, p, offset); + Foo[] array = Foo.CreateArray(4); + int start = 2; + fixed (Foo* p = array) + { + // 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); + } + } - // Assert: - Assert.Equal(array, ap.Array); - Assert.Equal(offset, ap.Offset); - Assert.Equal((IntPtr)(p+offset), ap.PointerAtOffset); + [Fact] + public void WithStartAndLength() + { + 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 Slice() + public class Slice { - Foo[] array = Foo.CreateArray(5); - int offset0 = 2; - int offset1 = 2; - int totalOffset = offset0 + offset1; - fixed (Foo* p = array) + [Fact] + public void StartOnly() { - BufferPointer ap = new BufferPointer(array, p, offset0); + Foo[] array = Foo.CreateArray(5); + int start0 = 2; + int start1 = 2; + int totalOffset = start0 + start1; - // Act: - ap = ap.Slice(offset1); + fixed (Foo* p = array) + { + BufferSpan span = new BufferSpan(array, p, start0); + + // Act: + span = span.Slice(start1); - // Assert: - Assert.Equal(array, ap.Array); - Assert.Equal(totalOffset, ap.Offset); - Assert.Equal((IntPtr)(p + totalOffset), 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 StartAndLength() + { + Foo[] array = Foo.CreateArray(10); + int start0 = 2; + int start1 = 2; + int totalOffset = start0 + start1; + int sliceLength = 3; + + fixed (Foo* p = array) + { + BufferSpan span = new BufferSpan(array, p, start0); + + // 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 +196,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); @@ -194,10 +248,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 +275,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 +302,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 +330,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 +357,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 +384,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 +409,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,11 +432,11 @@ 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]); } diff --git a/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs index d47bd5b94e..9eb12a536a 100644 --- a/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs +++ b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs @@ -25,7 +25,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); @@ -42,7 +42,7 @@ { Assert.False(buffer.IsDisposedOrLostArrayOwnership); Assert.Equal(array, buffer.Array); - Assert.Equal(count, buffer.Count); + Assert.Equal(count, buffer.Length); VerifyPointer(buffer); } @@ -71,7 +71,7 @@ { using (PinnedBuffer buffer = PinnedBuffer.CreateClean(42)) { - for (int j = 0; j < buffer.Count; j++) + for (int j = 0; j < buffer.Length; j++) { Assert.Equal(0, buffer.Array[j]); buffer.Array[j] = 666; @@ -96,10 +96,10 @@ using (PinnedBuffer buffer = new PinnedBuffer(a)) { - BufferPointer arrayPtr = buffer.Slice(); + BufferSpan arrayPtr = buffer.Slice(); Assert.Equal(a, arrayPtr.Array); - Assert.Equal(0, arrayPtr.Offset); + Assert.Equal(0, arrayPtr.Start); Assert.Equal(buffer.Pointer, arrayPtr.PointerAtOffset); } } From fa40b262169977d3348e201e020fa4c37f8320da Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 18 Mar 2017 02:10:37 +0100 Subject: [PATCH 27/83] introduced PinnedImageBuffer --- src/ImageSharp/Common/Memory/BufferSpan{T}.cs | 13 ++- .../Common/Memory/IPinnedImageBuffer{T}.cs | 31 +++++++ .../Common/Memory/PinnedBuffer{T}.cs | 32 ++++--- .../Memory/PinnedImageBufferExtensions.cs | 45 ++++++++++ .../Common/Memory/PinnedImageBuffer{T}.cs | 62 +++++++++++++ src/ImageSharp/Image/PixelAccessor{TColor}.cs | 51 ++++++----- src/ImageSharp/Image/PixelArea{TColor}.cs | 2 +- .../Colors/BulkPixelOperationsTests.cs | 4 +- .../Common/PinnedBufferTests.cs | 88 +++++++++++++++---- .../Common/PinnedImageBufferTests.cs | 86 ++++++++++++++++++ 10 files changed, 355 insertions(+), 59 deletions(-) create mode 100644 src/ImageSharp/Common/Memory/IPinnedImageBuffer{T}.cs create mode 100644 src/ImageSharp/Common/Memory/PinnedImageBufferExtensions.cs create mode 100644 src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs create mode 100644 tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs diff --git a/src/ImageSharp/Common/Memory/BufferSpan{T}.cs b/src/ImageSharp/Common/Memory/BufferSpan{T}.cs index 0507173e90..f3cb858847 100644 --- a/src/ImageSharp/Common/Memory/BufferSpan{T}.cs +++ b/src/ImageSharp/Common/Memory/BufferSpan{T}.cs @@ -180,12 +180,14 @@ namespace ImageSharp } /// - /// Clears `count` elements beginning from the pointed position. + /// 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)); @@ -196,6 +198,15 @@ namespace ImageSharp } } + /// + /// Clears the the span + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear() + { + this.Clear(this.Length); + } + [Conditional("DEBUG")] private static void GuardArrayAndPointer(T[] array, void* pointerToArray) { 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 7378a8d640..d58354a71e 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. @@ -56,9 +56,9 @@ namespace ImageSharp /// /// Initializes a new instance of the class. /// - /// The count of "relevant" elements in 'array'. /// The array to pin. - public PinnedBuffer(int length, T[] array) + /// The count of "relevant" elements in 'array'. + public PinnedBuffer(T[] array, int length) { if (array.Length < length) { @@ -99,14 +99,19 @@ namespace ImageSharp /// public IntPtr Pointer { get; private set; } + /// + /// Gets a to the backing buffer. + /// + public BufferSpan Span => this; + /// /// Converts to an . /// /// The to convert. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator BufferSpan(PinnedBuffer buffer) + public static unsafe implicit operator BufferSpan(PinnedBuffer buffer) { - return buffer.Slice(); + return new BufferSpan(buffer.Array, (void*)buffer.Pointer, 0, buffer.Length); } /// @@ -114,6 +119,7 @@ namespace ImageSharp /// /// The desired count of elements. (Minimum size for ) /// The instance + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static PinnedBuffer CreateClean(int count) { PinnedBuffer buffer = new PinnedBuffer(count); @@ -122,24 +128,26 @@ namespace ImageSharp } /// - /// Gets a to the beginning of the raw data of the buffer. + /// Gets a to an offseted position inside the buffer. /// + /// The start /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe BufferSpan Slice() + public unsafe BufferSpan Slice(int start) { - return new BufferSpan(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. /// - /// The offset + /// The start + /// The length of the slice /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe BufferSpan Slice(int offset) + public unsafe BufferSpan Slice(int start, int length) { - return new BufferSpan(this.Array, (void*)this.Pointer, offset); + return new BufferSpan(this.Array, (void*)this.Pointer, start, length); } /// @@ -195,7 +203,7 @@ namespace ImageSharp [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear() { - this.Slice().Clear(this.Length); + ((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..6ec0fc4e56 --- /dev/null +++ b/src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs @@ -0,0 +1,62 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// 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; } + + /// + /// 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/Image/PixelAccessor{TColor}.cs b/src/ImageSharp/Image/PixelAccessor{TColor}.cs index d22deec742..e21e3aa46f 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, PinnedBuffer.CreateClean(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; /// @@ -244,9 +247,9 @@ namespace ImageSharp /// The x coordinate /// The y coordinate /// The - internal BufferSpan GetRowPointer(int x, int y) + internal BufferSpan GetRowSpan(int x, int y) { - return this.pixelBuffer.Slice((y * this.Width) + x); + return this.pixelBuffer.Slice((y * this.Width) + x, this.Width - x); } /// @@ -288,8 +291,8 @@ namespace ImageSharp { for (int y = 0; y < height; y++) { - BufferSpan source = area.GetRowPointer(y); - BufferSpan 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 +311,8 @@ namespace ImageSharp { for (int y = 0; y < height; y++) { - BufferSpan source = area.GetRowPointer(y); - BufferSpan 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 +331,8 @@ namespace ImageSharp { for (int y = 0; y < height; y++) { - BufferSpan source = area.GetRowPointer(y); - BufferSpan 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 +351,8 @@ namespace ImageSharp { for (int y = 0; y < height; y++) { - BufferSpan source = area.GetRowPointer(y); - BufferSpan 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 +370,8 @@ namespace ImageSharp { for (int y = 0; y < height; y++) { - BufferSpan source = this.GetRowPointer(sourceX, sourceY + y); - BufferSpan destination = area.GetRowPointer(y); + BufferSpan source = this.GetRowSpan(sourceX, sourceY + y); + BufferSpan destination = area.GetRowSpan(y); Operations.ToZyxBytes(source, destination, width); } } @@ -386,8 +389,8 @@ namespace ImageSharp { for (int y = 0; y < height; y++) { - BufferSpan source = this.GetRowPointer(sourceX, sourceY + y); - BufferSpan destination = area.GetRowPointer(y); + BufferSpan source = this.GetRowSpan(sourceX, sourceY + y); + BufferSpan destination = area.GetRowSpan(y); Operations.ToZyxwBytes(source, destination, width); } } @@ -405,8 +408,8 @@ namespace ImageSharp { for (int y = 0; y < height; y++) { - BufferSpan source = this.GetRowPointer(sourceX, sourceY + y); - BufferSpan destination = area.GetRowPointer(y); + BufferSpan source = this.GetRowSpan(sourceX, sourceY + y); + BufferSpan destination = area.GetRowSpan(y); Operations.ToXyzBytes(source, destination, width); } } @@ -424,15 +427,15 @@ namespace ImageSharp { for (int y = 0; y < height; y++) { - BufferSpan source = this.GetRowPointer(sourceX, sourceY + y); - BufferSpan 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 +444,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 0a3c5710ce..bd10c9b6b0 100644 --- a/src/ImageSharp/Image/PixelArea{TColor}.cs +++ b/src/ImageSharp/Image/PixelArea{TColor}.cs @@ -206,7 +206,7 @@ namespace ImageSharp /// /// The y coordinate /// The - internal BufferSpan GetRowPointer(int y) + internal BufferSpan GetRowSpan(int y) { return this.byteBuffer.Slice(y * this.RowStride); } diff --git a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs index 141c9d888d..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 BufferSpan Source => this.SourceBuffer.Slice(); - public BufferSpan ActualDest => this.ActualDestBuffer.Slice(); + public BufferSpan Source => this.SourceBuffer; + public BufferSpan ActualDest => this.ActualDestBuffer; public TestBuffers(TSource[] source, TDest[] expectedDest) { diff --git a/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs index 9eb12a536a..f7189aadfa 100644 --- a/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs +++ b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs @@ -66,18 +66,17 @@ [Fact] public void CreateClean() { - Parallel.For(0, 100, - i => + for (int i = 0; i < 100; i++) + { + using (PinnedBuffer buffer = PinnedBuffer.CreateClean(42)) + { + for (int j = 0; j < buffer.Length; j++) { - using (PinnedBuffer buffer = PinnedBuffer.CreateClean(42)) - { - for (int j = 0; j < buffer.Length; j++) - { - Assert.Equal(0, buffer.Array[j]); - buffer.Array[j] = 666; - } - } - }); + Assert.Equal(0, buffer.Array[j]); + buffer.Array[j] = 666; + } + } + } } [Fact] @@ -89,21 +88,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)) { - BufferSpan arrayPtr = buffer.Slice(); + BufferSpan span = buffer.Span; - Assert.Equal(a, arrayPtr.Array); - Assert.Equal(0, arrayPtr.Start); - 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..fa97307983 --- /dev/null +++ b/tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs @@ -0,0 +1,86 @@ +// ReSharper disable InconsistentNaming +namespace ImageSharp.Tests.Common +{ + using System.Runtime.CompilerServices; + + using Xunit; + + 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) + { + int[] array = new int[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(int) * 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(int) * (width * y + x), span.PointerAtOffset); + } + } + } +} \ No newline at end of file From b9545631c12a0faf5ec8f4c13b8171fe91d6583b Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sat, 18 Mar 2017 09:18:12 +0000 Subject: [PATCH 28/83] drop callbacks test by mocking encoder --- src/ImageSharp/Configuration.cs | 19 +++ src/ImageSharp/Image/IImageCallbacks.cs | 46 ------- src/ImageSharp/Image/ImageBase{TColor}.cs | 12 +- src/ImageSharp/Image/Image{TColor}.cs | 5 +- .../Drawing/Paths/ProcessorWatchingImage.cs | 13 +- .../ImageSharp.Tests/Image/ImageSaveTests.cs | 113 ++++++++---------- .../Image/SaveWatchingImage.cs | 53 -------- 7 files changed, 71 insertions(+), 190 deletions(-) delete mode 100644 src/ImageSharp/Image/IImageCallbacks.cs delete mode 100644 tests/ImageSharp.Tests/Image/SaveWatchingImage.cs diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index e0eb21865e..fa983d3557 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -34,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. /// diff --git a/src/ImageSharp/Image/IImageCallbacks.cs b/src/ImageSharp/Image/IImageCallbacks.cs deleted file mode 100644 index 93267de057..0000000000 --- a/src/ImageSharp/Image/IImageCallbacks.cs +++ /dev/null @@ -1,46 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - using ImageSharp.Processing; - - /// - /// Provides a set of methods that are called as part of the images lifetime/processes. - /// - internal interface IImageCallbacks - { - /// - /// Invoked before the image is saved. - /// - /// The color - /// The image - /// The destination stream - /// The encoder - /// The options - /// - /// return true if the processor should be applied otherwise false. - /// - bool OnSaving(ImageBase image, Stream stream, Formats.IImageEncoder encoder, IEncoderOptions options) - where TColor : struct, IPixel; - - /// - /// Invoked before the image is processed. - /// - /// The color - /// The image - /// The processor. - /// The rectangle. - /// - /// return true if the processor should be applied otherwise false. - /// - bool OnProcessing(ImageBase image, IImageProcessor processor, Rectangle rectangle) - where TColor : struct, IPixel; - } -} diff --git a/src/ImageSharp/Image/ImageBase{TColor}.cs b/src/ImageSharp/Image/ImageBase{TColor}.cs index f905123132..878ba09b39 100644 --- a/src/ImageSharp/Image/ImageBase{TColor}.cs +++ b/src/ImageSharp/Image/ImageBase{TColor}.cs @@ -116,11 +116,6 @@ namespace ImageSharp /// public Configuration Configuration { get; private set; } - /// - /// Gets or sets the callbacks item which will be called during the lifetime of the image being processed. - /// - internal IImageCallbacks Callbacks { get; set; } - /// /// Applies the processor. /// @@ -128,12 +123,7 @@ namespace ImageSharp /// The rectangle. public virtual void ApplyProcessor(IImageProcessor processor, Rectangle rectangle) { - // this will be null true or false, if its null or true then apply the processor - // thus is not false then apply the processors (allows for tests to save time and not actually run the prcessor is required) - if (this.Callbacks?.OnProcessing(this, processor, rectangle) != false) - { - processor.Apply(this, rectangle); - } + processor.Apply(this, rectangle); } /// diff --git a/src/ImageSharp/Image/Image{TColor}.cs b/src/ImageSharp/Image/Image{TColor}.cs index 8e33715bb8..3a76cf63ff 100644 --- a/src/ImageSharp/Image/Image{TColor}.cs +++ b/src/ImageSharp/Image/Image{TColor}.cs @@ -594,10 +594,7 @@ namespace ImageSharp Guard.NotNull(stream, nameof(stream)); Guard.NotNull(encoder, nameof(encoder)); - if (this.Callbacks?.OnSaving(this, stream, encoder, options) != false) - { - encoder.Encode(this, stream, options); - } + encoder.Encode(this, stream, options); } /// diff --git a/tests/ImageSharp.Tests/Drawing/Paths/ProcessorWatchingImage.cs b/tests/ImageSharp.Tests/Drawing/Paths/ProcessorWatchingImage.cs index ff4432fd16..2d3d2cc2b8 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/ProcessorWatchingImage.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/ProcessorWatchingImage.cs @@ -12,29 +12,22 @@ namespace ImageSharp.Tests.Drawing.Paths /// Watches but does not actually run the processors against the image. /// /// - public class ProcessorWatchingImage : Image, IImageCallbacks + public class ProcessorWatchingImage : Image { public List ProcessorApplications { get; } = new List(); public ProcessorWatchingImage(int width, int height) : base(width, height, Configuration.CreateDefaultInstance()) { - this.Callbacks = this; } - public bool OnSaving(ImageBase image, Stream stream, IImageEncoder encoder, IEncoderOptions options) where TColor : struct, IPixel - { - return true; - } - - public bool OnProcessing(ImageBase image, IImageProcessor processor, Rectangle rectangle) where TColor : struct, IPixel + public override void ApplyProcessor(IImageProcessor processor, Rectangle rectangle) { this.ProcessorApplications.Add(new ProcessorDetails { - processor = (IImageProcessor)processor, + processor = processor, rectangle = rectangle }); - return false;// do not really apply the processor to speed up testing } public struct ProcessorDetails diff --git a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs index 172a14dc2a..0d1c3e09b5 100644 --- a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs @@ -18,15 +18,38 @@ namespace ImageSharp.Tests /// public class ImageSaveTests : IDisposable { - private readonly SaveWatchingImage Image; + 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 SaveWatchingImage(1, 1, this.fileSystem.Object); + this.Image = new Image(1, 1, new Configuration(this.format.Object) { + FileSystem = this.fileSystem.Object + }); } [Fact] @@ -36,10 +59,7 @@ namespace ImageSharp.Tests this.fileSystem.Setup(x => x.Create("path.png")).Returns(stream); this.Image.Save("path.png"); - SaveWatchingImage.OperationDetails operation = this.Image.Saves.Single(); - Assert.Equal(stream, operation.stream); - Assert.IsType(operation.encoder); - Assert.Null(operation.options); + this.encoder.Verify(x => x.Encode(this.Image, stream, null)); } [Fact] @@ -49,11 +69,8 @@ namespace ImageSharp.Tests this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream); this.Image.Save("path.jpg", this.encoderOptions); - - SaveWatchingImage.OperationDetails operation = this.Image.Saves.Single(); - Assert.Equal(stream, operation.stream); - Assert.IsType(operation.encoder); - Assert.Equal(this.encoderOptions, operation.options); + + this.encoder.Verify(x => x.Encode(this.Image, stream, this.encoderOptions)); } [Fact] @@ -62,12 +79,9 @@ namespace ImageSharp.Tests Stream stream = new MemoryStream(); this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream); - this.Image.Save("path.jpg", new BmpEncoder()); + this.Image.Save("path.jpg", this.encoderNotInFormat.Object); - SaveWatchingImage.OperationDetails operation = this.Image.Saves.Single(); - Assert.Equal(stream, operation.stream); - Assert.IsType(operation.encoder); - Assert.Null(operation.options); + this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, null)); } [Fact] @@ -76,12 +90,9 @@ namespace ImageSharp.Tests Stream stream = new MemoryStream(); this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream); - this.Image.Save("path.jpg", new BmpEncoder(), this.encoderOptions); + this.Image.Save("path.jpg", this.encoderNotInFormat.Object, this.encoderOptions); - SaveWatchingImage.OperationDetails operation = this.Image.Saves.Single(); - Assert.Equal(stream, operation.stream); - Assert.IsType(operation.encoder); - Assert.Equal(this.encoderOptions, operation.options); + this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, this.encoderOptions)); } @@ -92,12 +103,9 @@ namespace ImageSharp.Tests Stream stream = new MemoryStream(); this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream); - this.Image.Save("path.jpg", new GifFormat()); + this.Image.Save("path.jpg", this.encoderNotInFormat.Object); - SaveWatchingImage.OperationDetails operation = this.Image.Saves.Single(); - Assert.Equal(stream, operation.stream); - Assert.IsType(operation.encoder); - Assert.Null(operation.options); + this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, null)); } [Fact] @@ -106,29 +114,18 @@ namespace ImageSharp.Tests Stream stream = new MemoryStream(); this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream); - this.Image.Save("path.jpg", new BmpFormat(), this.encoderOptions); + this.Image.Save("path.jpg", this.encoderNotInFormat.Object, this.encoderOptions); - SaveWatchingImage.OperationDetails operation = this.Image.Saves.Single(); - Assert.Equal(stream, operation.stream); - Assert.IsType(operation.encoder); - Assert.Equal(this.encoderOptions, operation.options); + this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, this.encoderOptions)); } - /// - /// ///////////////////////////////////////////////////////////// - /// - /// - [Fact] public void SaveStream() { Stream stream = new MemoryStream(); this.Image.Save(stream); - - SaveWatchingImage.OperationDetails operation = this.Image.Saves.Single(); - Assert.Equal(stream, operation.stream); - Assert.IsType(this.Image.CurrentImageFormat.Encoder.GetType(), operation.encoder); - Assert.Null(operation.options); + + this.encoder.Verify(x => x.Encode(this.Image, stream, null)); } [Fact] @@ -138,11 +135,7 @@ namespace ImageSharp.Tests this.Image.Save(stream, this.encoderOptions); - SaveWatchingImage.OperationDetails operation = this.Image.Saves.Single(); - Assert.Equal(stream, operation.stream); - Assert.IsType(this.Image.CurrentImageFormat.Encoder.GetType(), operation.encoder); - - Assert.Equal(this.encoderOptions, operation.options); + this.encoder.Verify(x => x.Encode(this.Image, stream, this.encoderOptions)); } [Fact] @@ -150,12 +143,9 @@ namespace ImageSharp.Tests { Stream stream = new MemoryStream(); - this.Image.Save(stream, new BmpEncoder()); + this.Image.Save(stream, this.encoderNotInFormat.Object); - SaveWatchingImage.OperationDetails operation = this.Image.Saves.Single(); - Assert.Equal(stream, operation.stream); - Assert.IsType(operation.encoder); - Assert.Null(operation.options); + this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, null)); } [Fact] @@ -163,12 +153,9 @@ namespace ImageSharp.Tests { Stream stream = new MemoryStream(); - this.Image.Save(stream, new BmpEncoder(), this.encoderOptions); + this.Image.Save(stream, this.encoderNotInFormat.Object, this.encoderOptions); - SaveWatchingImage.OperationDetails operation = this.Image.Saves.Single(); - Assert.Equal(stream, operation.stream); - Assert.IsType(operation.encoder); - Assert.Equal(this.encoderOptions, operation.options); + this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, this.encoderOptions)); } [Fact] @@ -176,12 +163,9 @@ namespace ImageSharp.Tests { Stream stream = new MemoryStream(); - this.Image.Save(stream, new GifFormat()); + this.Image.Save(stream, this.formatNotRegistered.Object); - SaveWatchingImage.OperationDetails operation = this.Image.Saves.Single(); - Assert.Equal(stream, operation.stream); - Assert.IsType(operation.encoder); - Assert.Null(operation.options); + this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, null)); } [Fact] @@ -189,12 +173,9 @@ namespace ImageSharp.Tests { Stream stream = new MemoryStream(); - this.Image.Save(stream, new BmpFormat(), this.encoderOptions); + this.Image.Save(stream, this.formatNotRegistered.Object, this.encoderOptions); - SaveWatchingImage.OperationDetails operation = this.Image.Saves.Single(); - Assert.Equal(stream, operation.stream); - Assert.IsType(operation.encoder); - Assert.Equal(this.encoderOptions, operation.options); + this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, this.encoderOptions)); } public void Dispose() diff --git a/tests/ImageSharp.Tests/Image/SaveWatchingImage.cs b/tests/ImageSharp.Tests/Image/SaveWatchingImage.cs deleted file mode 100644 index 0436146913..0000000000 --- a/tests/ImageSharp.Tests/Image/SaveWatchingImage.cs +++ /dev/null @@ -1,53 +0,0 @@ - -namespace ImageSharp.Tests -{ - using System; - using System.IO; - using ImageSharp; - using Processing; - using System.Collections.Generic; - using ImageSharp.Formats; - using ImageSharp.IO; - - /// - /// Watches but does not actually run the processors against the image. - /// - /// - public class SaveWatchingImage : Image, IImageCallbacks - { - public List Saves { get; } = new List(); - - public SaveWatchingImage(int width, int height, IFileSystem fs = null) - : base(width, height, Configuration.CreateDefaultInstance()) - { - //switch out the file system for tests - this.Configuration.FileSystem = fs ?? this.Configuration.FileSystem; - - this.Callbacks = this; - } - - public bool OnSaving(ImageBase image, Stream stream, IImageEncoder encoder, IEncoderOptions options) where TColor : struct, IPixel - { - this.Saves.Add(new OperationDetails - { - encoder = encoder, - options = options, - stream = stream - }); - - return false; - } - - public bool OnProcessing(ImageBase image, IImageProcessor processor, Rectangle rectangle) where TColor : struct, IPixel - { - return false; - } - - public struct OperationDetails - { - public Stream stream; - public IImageEncoder encoder; - public IEncoderOptions options; - } - } -} From a6e4de8d56918d89c3bd54c0e904a67e650112e6 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sat, 18 Mar 2017 09:46:03 +0000 Subject: [PATCH 29/83] add guards to verify scanlineBuffer size --- src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs | 7 +++++-- src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs | 7 +++++-- .../Brushes/Processors/BrushApplicator.cs | 9 ++++++--- src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs | 7 +++++-- src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs | 7 +++++-- 5 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs index f3ea81cf61..636f3a5a4c 100644 --- a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs @@ -112,9 +112,12 @@ namespace ImageSharp.Drawing.Brushes this.source.Dispose(); } - internal override void Apply(float[] scanline, int scanlineWidth, int offset, int x, int y) + /// + internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) { - using (PinnedBuffer buffer = new PinnedBuffer(scanline)) + DebugGuard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); + + using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) { BufferPointer slice = buffer.Slice(offset); diff --git a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs index 55152d234f..c718ce1f4a 100644 --- a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs @@ -144,9 +144,12 @@ namespace ImageSharp.Drawing.Brushes // noop } - internal override void Apply(float[] scanline, int scanlineWidth, int offset, int x, int y) + /// + internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) { - using (PinnedBuffer buffer = new PinnedBuffer(scanline)) + DebugGuard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); + + using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) { BufferPointer slice = buffer.Slice(offset); diff --git a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs index 679d871700..a4f1eeaed2 100644 --- a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs +++ b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs @@ -45,14 +45,17 @@ namespace ImageSharp.Drawing.Processors /// /// 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 burshed color value before being applied to the target. + /// 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. - internal virtual void Apply(float[] scanline, int scanlineWidth, int offset, int x, int y) + /// 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) { - using (PinnedBuffer buffer = new PinnedBuffer(scanline)) + DebugGuard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); + + using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) { BufferPointer slice = buffer.Slice(offset); diff --git a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs index 92e5191610..cdfc23b913 100644 --- a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs @@ -136,9 +136,12 @@ namespace ImageSharp.Drawing.Brushes { } - internal override void Apply(float[] scanline, int scanlineWidth, int offset, int x, int y) + /// + internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) { - using (PinnedBuffer buffer = new PinnedBuffer(scanline)) + DebugGuard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); + + using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) { BufferPointer slice = buffer.Slice(offset); diff --git a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs index bdac7fdc71..e3413328e0 100644 --- a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs @@ -84,9 +84,12 @@ namespace ImageSharp.Drawing.Brushes // noop } - internal override void Apply(float[] scanline, int scanlineWidth, int offset, int x, int y) + /// + internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) { - using (PinnedBuffer buffer = new PinnedBuffer(scanline)) + DebugGuard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); + + using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) { BufferPointer slice = buffer.Slice(offset); From 838ec08700855547d534a2ecefdad0fb00643094 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sat, 18 Mar 2017 09:51:35 +0000 Subject: [PATCH 30/83] fix broken comment --- src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs index a4f1eeaed2..2c460e88e0 100644 --- a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs +++ b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs @@ -47,7 +47,7 @@ namespace ImageSharp.Drawing.Processors /// /// 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 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. From 13a5d8edf56edbe0da2313d26551e7cacc055e47 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sat, 18 Mar 2017 10:21:31 +0000 Subject: [PATCH 31/83] remove redundant SaveInternal --- src/ImageSharp/Image/Image{TColor}.cs | 29 ++++++--------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/src/ImageSharp/Image/Image{TColor}.cs b/src/ImageSharp/Image/Image{TColor}.cs index 3a76cf63ff..34724cc977 100644 --- a/src/ImageSharp/Image/Image{TColor}.cs +++ b/src/ImageSharp/Image/Image{TColor}.cs @@ -349,9 +349,7 @@ namespace ImageSharp /// The public Image Save(Stream stream, IEncoderOptions options) { - Guard.NotNull(stream, nameof(stream)); - this.SaveInternal(stream, this.CurrentImageFormat?.Encoder, options); - return this; + return this.Save(stream, this.CurrentImageFormat?.Encoder, options); } /// @@ -376,9 +374,7 @@ namespace ImageSharp { Guard.NotNull(format, nameof(format)); - this.SaveInternal(stream, format.Encoder, options); - - return this; + return this.Save(stream, format.Encoder, options); } /// @@ -407,7 +403,10 @@ namespace ImageSharp /// public Image Save(Stream stream, IImageEncoder encoder, IEncoderOptions options) { - this.SaveInternal(stream, encoder, options); + Guard.NotNull(stream, nameof(stream)); + Guard.NotNull(encoder, nameof(encoder)); + + encoder.Encode(this, stream, options); return this; } @@ -581,22 +580,6 @@ namespace ImageSharp base.Dispose(disposing); } - /// - /// Internally saves the image to the given stream using the given image encoder and options. - /// Can be used by overridden by tests to verify save opperations. - /// - /// The stream to save the image to. - /// The encoder to save the image with. - /// The options for the encoder. - /// Thrown if the stream or encoder is null. - private void SaveInternal(Stream stream, IImageEncoder encoder, IEncoderOptions options) - { - Guard.NotNull(stream, nameof(stream)); - Guard.NotNull(encoder, nameof(encoder)); - - encoder.Encode(this, stream, options); - } - /// /// Copies the properties from the other . /// From 4e3b3b6ab16446a62cc4fc913bbc09fff9cb5117 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sat, 18 Mar 2017 11:46:36 +0000 Subject: [PATCH 32/83] Improve test coverage - fixed bug in pattern brush and added test coverage - change DebugGuard -> Guard - Update SixLabors.Shapes to version not doing array allocation on hot path. --- .../Brushes/ImageBrush{TColor}.cs | 2 +- .../Brushes/PatternBrush{TColor}.cs | 7 ++--- .../Brushes/RecolorBrush{TColor}.cs | 2 +- .../Brushes/SolidBrush{TColor}.cs | 2 +- .../ImageSharp.Drawing.csproj | 2 +- .../Drawing/FillPolygon.cs | 26 +++++++++++++++++++ .../Drawing/SolidPolygonTests.cs | 26 +++++++++++++++++++ 7 files changed, 60 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs index 636f3a5a4c..080111f610 100644 --- a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs @@ -115,7 +115,7 @@ namespace ImageSharp.Drawing.Brushes /// internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) { - DebugGuard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); + Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) { diff --git a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs index c718ce1f4a..2b4d3ec738 100644 --- a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs @@ -86,6 +86,7 @@ namespace ImageSharp.Drawing.Brushes internal PatternBrush(PatternBrush brush) { this.pattern = brush.pattern; + this.patternVector = brush.patternVector; } /// @@ -112,7 +113,7 @@ namespace ImageSharp.Drawing.Brushes /// The pattern. /// The patternVector. public PatternBrushApplicator(PixelAccessor sourcePixels, Fast2DArray pattern, Fast2DArray patternVector) - : base(sourcePixels) + : base(sourcePixels) { this.pattern = pattern; this.patternVector = patternVector; @@ -147,7 +148,7 @@ namespace ImageSharp.Drawing.Brushes /// internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) { - DebugGuard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); + Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) { @@ -164,7 +165,7 @@ namespace ImageSharp.Drawing.Brushes Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4(); // 2d array index at row/column - Vector4 sourceVector = this.patternVector[targetY % this.pattern.Height, targetX % this.pattern.Width]; + Vector4 sourceVector = this.patternVector[targetY % this.patternVector.Height, targetX % this.patternVector.Width]; Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); diff --git a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs index cdfc23b913..0c6e86643f 100644 --- a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs @@ -139,7 +139,7 @@ namespace ImageSharp.Drawing.Brushes /// internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) { - DebugGuard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); + Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) { diff --git a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs index e3413328e0..e001829b01 100644 --- a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs @@ -87,7 +87,7 @@ namespace ImageSharp.Drawing.Brushes /// internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) { - DebugGuard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); + Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) { diff --git a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj index f690dac8d5..e15bfe74ba 100644 --- a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj +++ b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj @@ -40,7 +40,7 @@ All - + ..\..\ImageSharp.ruleset 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.Tests/Drawing/SolidPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs index 6f9b31e367..79363480fc 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs @@ -43,6 +43,32 @@ namespace ImageSharp.Tests.Drawing } } + [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) + }; + + 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); + } + + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[81, 145]); + } + } + } + [Fact] public void ImageShouldBeOverlayedByFilledPolygonNoAntialias() { From 2d8836bfc1d492427f0780553fd27233d05eec8f Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sat, 18 Mar 2017 12:39:03 +0000 Subject: [PATCH 33/83] skip building branches with a PR open prevent building both branch and pr/merge when an active PR is open --- appveyor.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index c456a8d722..89cb576539 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')) From e2d780107cd9c2f4ff1a360d5e9c627ae3458436 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 18 Mar 2017 14:24:06 +0100 Subject: [PATCH 34/83] BufferSpan ref indexer + Fix Drawing usages --- .../Brushes/ImageBrush{TColor}.cs | 2 +- .../Brushes/PatternBrush{TColor}.cs | 2 +- .../Brushes/Processors/BrushApplicator.cs | 2 +- .../Brushes/RecolorBrush{TColor}.cs | 2 +- .../Brushes/SolidBrush{TColor}.cs | 2 +- src/ImageSharp/Common/Memory/BufferSpan{T}.cs | 17 +++++++ .../Common/BufferSpanTests.cs | 46 +++++++++++++++++++ 7 files changed, 68 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs index 080111f610..718705b39a 100644 --- a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs @@ -119,7 +119,7 @@ namespace ImageSharp.Drawing.Brushes using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) { - BufferPointer slice = buffer.Slice(offset); + BufferSpan slice = buffer.Slice(offset); for (int xPos = 0; xPos < scanlineWidth; xPos++) { diff --git a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs index 2b4d3ec738..df492a764e 100644 --- a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs @@ -152,7 +152,7 @@ namespace ImageSharp.Drawing.Brushes using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) { - BufferPointer slice = buffer.Slice(offset); + BufferSpan slice = buffer.Slice(offset); for (int xPos = 0; xPos < scanlineWidth; xPos++) { diff --git a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs index 2c460e88e0..46444e5503 100644 --- a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs +++ b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs @@ -57,7 +57,7 @@ namespace ImageSharp.Drawing.Processors using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) { - BufferPointer slice = buffer.Slice(offset); + BufferSpan slice = buffer.Slice(offset); for (int xPos = 0; xPos < scanlineWidth; xPos++) { diff --git a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs index 0c6e86643f..257eeb3ae5 100644 --- a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs @@ -143,7 +143,7 @@ namespace ImageSharp.Drawing.Brushes using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) { - BufferPointer slice = buffer.Slice(offset); + BufferSpan slice = buffer.Slice(offset); for (int xPos = 0; xPos < scanlineWidth; xPos++) { diff --git a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs index e001829b01..125b07bcac 100644 --- a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs @@ -91,7 +91,7 @@ namespace ImageSharp.Drawing.Brushes using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) { - BufferPointer slice = buffer.Slice(offset); + BufferSpan slice = buffer.Slice(offset); for (int xPos = 0; xPos < scanlineWidth; xPos++) { diff --git a/src/ImageSharp/Common/Memory/BufferSpan{T}.cs b/src/ImageSharp/Common/Memory/BufferSpan{T}.cs index f3cb858847..8ef88814cd 100644 --- a/src/ImageSharp/Common/Memory/BufferSpan{T}.cs +++ b/src/ImageSharp/Common/Memory/BufferSpan{T}.cs @@ -106,6 +106,23 @@ namespace ImageSharp /// 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 /// diff --git a/tests/ImageSharp.Tests/Common/BufferSpanTests.cs b/tests/ImageSharp.Tests/Common/BufferSpanTests.cs index f524bdb437..36cfe1b2a9 100644 --- a/tests/ImageSharp.Tests/Common/BufferSpanTests.cs +++ b/tests/ImageSharp.Tests/Common/BufferSpanTests.cs @@ -209,6 +209,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) @@ -443,6 +488,7 @@ namespace ImageSharp.Tests.Common } } + internal static bool ElementsAreEqual(Foo[] array, byte[] rawArray, int index) { fixed (Foo* pArray = array) From 8b91c02a274f833dfea9bd5dad577e86019970ad Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 18 Mar 2017 16:57:15 +0100 Subject: [PATCH 35/83] PinnedBuffer and PinnedImageBuffer indexers --- .../Common/Memory/PinnedBuffer{T}.cs | 17 +++++ .../Common/Memory/PinnedImageBuffer{T}.cs | 9 +++ src/ImageSharp/Image/PixelAccessor{TColor}.cs | 20 +++--- .../Common/BufferSpanTests.cs | 56 +---------------- .../Common/PinnedBufferTests.cs | 48 +++++++++++--- .../Common/PinnedImageBufferTests.cs | 34 +++++++--- tests/ImageSharp.Tests/Common/TestStructs.cs | 62 +++++++++++++++++++ 7 files changed, 168 insertions(+), 78 deletions(-) create mode 100644 tests/ImageSharp.Tests/Common/TestStructs.cs diff --git a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs index d58354a71e..611688c995 100644 --- a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs +++ b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs @@ -104,6 +104,23 @@ namespace ImageSharp /// 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 . /// diff --git a/src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs b/src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs index 6ec0fc4e56..380545d70f 100644 --- a/src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs +++ b/src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs @@ -6,6 +6,7 @@ namespace ImageSharp { using System; + using System.Runtime.CompilerServices; /// /// Represents a pinned buffer of value type objects @@ -46,6 +47,14 @@ namespace ImageSharp /// 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] => ref this.Array[(this.Width * y) + x]; + /// /// Creates a clean instance of initializing it's elements with 'default(T)'. /// diff --git a/src/ImageSharp/Image/PixelAccessor{TColor}.cs b/src/ImageSharp/Image/PixelAccessor{TColor}.cs index e21e3aa46f..cea058951e 100644 --- a/src/ImageSharp/Image/PixelAccessor{TColor}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TColor}.cs @@ -241,16 +241,16 @@ namespace ImageSharp this.CopyTo(area, sourceX, sourceY, width, height); } - /// - /// Gets a to the row 'y' beginning from the pixel at 'x'. - /// - /// The x coordinate - /// The y coordinate - /// The - internal BufferSpan GetRowSpan(int x, int y) - { - return this.pixelBuffer.Slice((y * this.Width) + x, this.Width - x); - } + ///// + ///// Gets a to the row 'y' beginning from the pixel at 'x'. + ///// + ///// The x coordinate + ///// The y coordinate + ///// The + //internal BufferSpan GetRowSpan(int x, int y) + //{ + // return this.pixelBuffer.Slice((y * this.Width) + x, this.Width - x); + //} /// /// Sets the pixel buffer in an unsafe manner. This should not be used unless you know what its doing!!! diff --git a/tests/ImageSharp.Tests/Common/BufferSpanTests.cs b/tests/ImageSharp.Tests/Common/BufferSpanTests.cs index 36cfe1b2a9..aee032accd 100644 --- a/tests/ImageSharp.Tests/Common/BufferSpanTests.cs +++ b/tests/ImageSharp.Tests/Common/BufferSpanTests.cs @@ -7,62 +7,10 @@ namespace ImageSharp.Tests.Common using Xunit; + using static TestStructs; + public unsafe class BufferSpanTests { - 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 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; - } - } - [Fact] public void AsBytes() { diff --git a/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs index f7189aadfa..5e812d5a01 100644 --- a/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs +++ b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs @@ -7,15 +7,10 @@ using Xunit; + using static TestStructs; + public unsafe class PinnedBufferTests { - public struct Foo - { - public int A; - - public double B; - } - [Theory] [InlineData(42)] [InlineData(1111)] @@ -79,6 +74,45 @@ } } + 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() { diff --git a/tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs b/tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs index fa97307983..7756b0aa98 100644 --- a/tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs +++ b/tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs @@ -5,6 +5,8 @@ namespace ImageSharp.Tests.Common using Xunit; + using static TestStructs; + public unsafe class PinnedImageBufferTests { [Theory] @@ -12,7 +14,7 @@ namespace ImageSharp.Tests.Common [InlineData(1025, 17)] public void Construct(int width, int height) { - using (PinnedImageBuffer buffer = new PinnedImageBuffer(width, height)) + using (PinnedImageBuffer buffer = new PinnedImageBuffer(width, height)) { Assert.Equal(width, buffer.Width); Assert.Equal(height, buffer.Height); @@ -25,8 +27,8 @@ namespace ImageSharp.Tests.Common [InlineData(1025, 17)] public void Construct_FromExternalArray(int width, int height) { - int[] array = new int[width * height + 10]; - using (PinnedImageBuffer buffer = new PinnedImageBuffer(array, width, 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); @@ -57,9 +59,9 @@ namespace ImageSharp.Tests.Common [InlineData(17, 42, 41)] public void GetRowSpanY(int width, int height, int y) { - using (PinnedImageBuffer buffer = new PinnedImageBuffer(width, height)) + using (PinnedImageBuffer buffer = new PinnedImageBuffer(width, height)) { - BufferSpan span = buffer.GetRowSpan(y); + BufferSpan span = buffer.GetRowSpan(y); Assert.Equal(width * y, span.Start); Assert.Equal(width, span.Length); @@ -73,14 +75,32 @@ namespace ImageSharp.Tests.Common [InlineData(17, 42, 0, 41)] public void GetRowSpanXY(int width, int height, int x, int y) { - using (PinnedImageBuffer buffer = new PinnedImageBuffer(width, height)) + using (PinnedImageBuffer buffer = new PinnedImageBuffer(width, height)) { - BufferSpan span = buffer.GetRowSpan(x, y); + BufferSpan span = buffer.GetRowSpan(x, y); Assert.Equal(width * y + x, span.Start); Assert.Equal(width - x, span.Length); Assert.Equal(buffer.Pointer + sizeof(int) * (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 From 378c2348861e15e91bb4851a0383459f1e02bc1e Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 18 Mar 2017 18:17:54 +0100 Subject: [PATCH 36/83] refactor ResamplingWeightedProcessor.Weights --- src/ImageSharp/Image/PixelAccessor{TColor}.cs | 11 -- .../Transforms/CompandingResizeProcessor.cs | 24 ++-- .../Transforms/ResamplingWeightedProcessor.cs | 117 +++++++++++------- .../Processors/Transforms/ResizeProcessor.cs | 63 ++++++---- 4 files changed, 127 insertions(+), 88 deletions(-) diff --git a/src/ImageSharp/Image/PixelAccessor{TColor}.cs b/src/ImageSharp/Image/PixelAccessor{TColor}.cs index cea058951e..f5393cfb38 100644 --- a/src/ImageSharp/Image/PixelAccessor{TColor}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TColor}.cs @@ -241,17 +241,6 @@ namespace ImageSharp this.CopyTo(area, sourceX, sourceY, width, height); } - ///// - ///// Gets a to the row 'y' beginning from the pixel at 'x'. - ///// - ///// The x coordinate - ///// The y coordinate - ///// The - //internal BufferSpan GetRowSpan(int x, int y) - //{ - // return this.pixelBuffer.Slice((y * this.Width) + x, this.Width - x); - //} - /// /// Sets the pixel buffer in an unsafe manner. This should not be used unless you know what its doing!!! /// diff --git a/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs index f5314d448d..14cea480d8 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs @@ -46,7 +46,7 @@ namespace ImageSharp.Processing.Processors public override bool Compand { get; set; } = true; /// - 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) @@ -119,15 +119,18 @@ namespace ImageSharp.Processing.Processors for (int x = minX; x < maxX; x++) { // Ensure offsets are normalised for cropping and padding. - Weight[] horizontalValues = this.HorizontalWeights[x - startX].Values; + Weights ws = this.HorizontalWeights.Weights[x - startX]; + float* horizontalValues = ws.Ptr; + int left = ws.Left; // Destination color components Vector4 destination = Vector4.Zero; - for (int i = 0; i < horizontalValues.Length; i++) + for (int i = 0; i < ws.Length; i++) { - Weight xw = horizontalValues[i]; - destination += sourcePixels[xw.Index + sourceX, y].ToVector4().Expand() * xw.Value; + float xw = horizontalValues[i]; + int index = left + i; + destination += sourcePixels[index, y].ToVector4().Expand() * xw; } TColor d = default(TColor); @@ -144,17 +147,20 @@ namespace ImageSharp.Processing.Processors y => { // Ensure offsets are normalised for cropping and padding. - Weight[] verticalValues = this.VerticalWeights[y - startY].Values; + Weights ws = this.VerticalWeights.Weights[y - startY]; + float* verticalValues = ws.Ptr; + int left = ws.Left; for (int x = 0; x < width; x++) { // Destination color components Vector4 destination = Vector4.Zero; - for (int i = 0; i < verticalValues.Length; i++) + for (int i = 0; i < ws.Length; i++) { - Weight yw = verticalValues[i]; - destination += firstPassPixels[x, yw.Index + sourceY].ToVector4().Expand() * yw.Value; + float yw = verticalValues[i]; + int index = left + i; + destination += firstPassPixels[x, index].ToVector4().Expand() * yw; } TColor d = default(TColor); diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs index 2d6de41545..51f382df2a 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs @@ -6,6 +6,9 @@ namespace ImageSharp.Processing.Processors { using System; + using System.Buffers; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; /// /// Provides methods that allow the resizing of images using various algorithms. @@ -59,32 +62,41 @@ namespace ImageSharp.Processing.Processors /// /// Gets or sets the horizontal weights. /// - protected Weights[] HorizontalWeights { get; set; } + protected Weights.Buffer HorizontalWeights { get; set; } /// /// Gets or sets the vertical weights. /// - protected Weights[] VerticalWeights { get; set; } + protected Weights.Buffer 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); + this.HorizontalWeights = this.PrecomputeWeights( + this.ResizeRectangle.Width, + sourceRectangle.Width); + + this.VerticalWeights = this.PrecomputeWeights( + this.ResizeRectangle.Height, + sourceRectangle.Height); } } + protected override void AfterApply(ImageBase source, Rectangle sourceRectangle) + { + base.AfterApply(source, sourceRectangle); + this.HorizontalWeights?.Dispose(); + this.HorizontalWeights = null; + this.VerticalWeights?.Dispose(); + this.VerticalWeights = null; + } + /// /// 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) + protected unsafe Weights.Buffer PrecomputeWeights(int destinationSize, int sourceSize) { float ratio = (float)sourceSize / destinationSize; float scale = ratio; @@ -96,8 +108,8 @@ namespace ImageSharp.Processing.Processors IResampler sampler = this.Sampler; float radius = (float)Math.Ceiling(scale * sampler.Radius); - Weights[] result = new Weights[destinationSize]; - + Weights.Buffer result = new Weights.Buffer(sourceSize, destinationSize); + for (int i = 0; i < destinationSize; i++) { float center = ((i + .5F) * ratio) - .5F; @@ -116,67 +128,82 @@ namespace ImageSharp.Processing.Processors } float sum = 0; - result[i] = new Weights(); - Weight[] weights = new Weight[right - left + 1]; + + result.Weights[i] = result.Slice(i, left, right); + Weights ws = result.Weights[i]; + + 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. + /// Represents a collection of weights and their sum. /// - protected struct Weight + protected unsafe struct Weights { /// - /// Initializes a new instance of the struct. + /// The local left index position /// - /// The index. - /// The value. - public Weight(int index, float value) + public int Left; + + public BufferSpan Span; + + public float* Ptr => (float*)Span.PointerAtOffset; + + public int Length => Span.Length; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Weights(int left, BufferSpan span) { - this.Index = index; - this.Value = value; + this.Left = left; + this.Span = span; } - /// - /// Gets the pixel index. - /// - public int Index { get; } + internal unsafe class Buffer : IDisposable + { + private PinnedImageBuffer dataBuffer; - /// - /// Gets or sets the result of the interpolation algorithm. - /// - public float Value { get; set; } - } + public Weights[] Weights { get; } - /// - /// Represents a collection of weights and their sum. - /// - protected class Weights - { - /// - /// Gets or sets the values. - /// - public Weight[] Values { get; set; } + public float* DataPtr { get; } + + internal Weights Slice(int i, int leftIdx, int rightIdx) + { + var span = dataBuffer.GetRowSpan(i).Slice(leftIdx, rightIdx - leftIdx); + return new Weights(leftIdx, span); + } + + public Buffer(int sourceSize, int destinationSize) + { + this.dataBuffer = new PinnedImageBuffer(sourceSize, destinationSize); + this.dataBuffer.Clear(); + this.DataPtr = (float*)this.dataBuffer.Pointer; + this.Weights = new Weights[destinationSize]; + } + + public void Dispose() + { + this.dataBuffer.Dispose(); + } + } } } } \ 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 a43745a057..39b739134d 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs @@ -45,7 +45,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) @@ -107,33 +107,47 @@ namespace ImageSharp.Processing.Processors 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); + + for (int x = minX; x < maxX; x++) + { + // Ensure offsets are normalised for cropping and padding. + + Weights ws = this.HorizontalWeights.Weights[x - startX]; + float* horizontalValues = ws.Ptr; + int left = ws.Left; + + // Destination color components + Vector4 destination = Vector4.Zero; + + for (int i = 0; i < ws.Length; i++) + { + float xw = horizontalValues[i]; + int index = left + i; + destination += tempRowBuffer[index] * xw; + } + + firstPassPixels[x, y] = destination; + } } - - TColor d = default(TColor); - d.PackFromVector4(destination); - firstPassPixels[x, y] = d; - } - }); + }); // Now process the rows. Parallel.For( @@ -143,17 +157,20 @@ namespace ImageSharp.Processing.Processors y => { // Ensure offsets are normalised for cropping and padding. - Weight[] verticalValues = this.VerticalWeights[y - startY].Values; + Weights ws = this.VerticalWeights.Weights[y - startY]; + float* verticalValues = ws.Ptr; + int left = ws.Left; for (int x = 0; x < width; x++) { // Destination color components Vector4 destination = Vector4.Zero; - for (int i = 0; i < verticalValues.Length; i++) + for (int i = 0; i < ws.Length; i++) { - Weight yw = verticalValues[i]; - destination += firstPassPixels[x, yw.Index + sourceY].ToVector4() * yw.Value; + float yw = verticalValues[i]; + int index = left + i; + destination += firstPassPixels[x, index] * yw; } TColor d = default(TColor); From c523077d0aa3c52aae8859702a354ea7397621de Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 18 Mar 2017 19:08:00 +0100 Subject: [PATCH 37/83] more refactor on Weights stuff --- .../Transforms/CompandingResizeProcessor.cs | 4 +- .../ResamplingWeightedProcessor.Weights.cs | 144 ++++++++++++++++++ .../Transforms/ResamplingWeightedProcessor.cs | 72 ++------- .../Processors/Transforms/ResizeProcessor.cs | 34 +---- 4 files changed, 164 insertions(+), 90 deletions(-) create mode 100644 src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs index 14cea480d8..c8a43b3d98 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs @@ -119,7 +119,7 @@ namespace ImageSharp.Processing.Processors for (int x = minX; x < maxX; x++) { // Ensure offsets are normalised for cropping and padding. - Weights ws = this.HorizontalWeights.Weights[x - startX]; + WeightsWindow ws = this.HorizontalWeights.Weights[x - startX]; float* horizontalValues = ws.Ptr; int left = ws.Left; @@ -147,7 +147,7 @@ namespace ImageSharp.Processing.Processors y => { // Ensure offsets are normalised for cropping and padding. - Weights ws = this.VerticalWeights.Weights[y - startY]; + WeightsWindow ws = this.VerticalWeights.Weights[y - startY]; float* verticalValues = ws.Ptr; int left = ws.Left; 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..00a81100d2 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs @@ -0,0 +1,144 @@ +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 . + /// + protected unsafe struct WeightsWindow + { + /// + /// The local left index position + /// + public int Left; + + /// + /// The span of weights pointing to . + /// + 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 + public Vector4 ComputeWeightedRowSum(BufferSpan rowSpan) + { + float* horizontalValues = this.Ptr; + int left = this.Left; + + // Destination color components + Vector4 result = Vector4.Zero; + + for (int i = 0; i < this.Length; i++) + { + float xw = horizontalValues[i]; + int index = left + i; + result += rowSpan[index] * xw; + } + + 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 + 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. + /// + protected 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 = new PinnedImageBuffer(sourceSize, destinationSize); + this.dataBuffer.Clear(); + 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); + 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 51f382df2a..f74bf7edd2 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs @@ -7,7 +7,6 @@ namespace ImageSharp.Processing.Processors { using System; using System.Buffers; - using System.Runtime.CompilerServices; using System.Runtime.InteropServices; /// @@ -15,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// Adapted from /// /// The pixel format. - internal abstract class ResamplingWeightedProcessor : ImageProcessor + internal abstract partial class ResamplingWeightedProcessor : ImageProcessor where TColor : struct, IPixel { /// @@ -62,12 +61,12 @@ namespace ImageSharp.Processing.Processors /// /// Gets or sets the horizontal weights. /// - protected Weights.Buffer HorizontalWeights { get; set; } + protected WeightsBuffer HorizontalWeights { get; set; } /// /// Gets or sets the vertical weights. /// - protected Weights.Buffer VerticalWeights { get; set; } + protected WeightsBuffer VerticalWeights { get; set; } /// protected override void BeforeApply(ImageBase source, Rectangle sourceRectangle) @@ -84,6 +83,7 @@ namespace ImageSharp.Processing.Processors } } + /// protected override void AfterApply(ImageBase source, Rectangle sourceRectangle) { base.AfterApply(source, sourceRectangle); @@ -96,7 +96,10 @@ namespace ImageSharp.Processing.Processors /// /// Computes the weights to apply at each pixel when resizing. /// - protected unsafe Weights.Buffer PrecomputeWeights(int destinationSize, int sourceSize) + /// The destination size + /// The source size + /// The + protected unsafe WeightsBuffer PrecomputeWeights(int destinationSize, int sourceSize) { float ratio = (float)sourceSize / destinationSize; float scale = ratio; @@ -108,8 +111,8 @@ namespace ImageSharp.Processing.Processors IResampler sampler = this.Sampler; float radius = (float)Math.Ceiling(scale * sampler.Radius); - Weights.Buffer result = new Weights.Buffer(sourceSize, destinationSize); - + WeightsBuffer result = new WeightsBuffer(sourceSize, destinationSize); + for (int i = 0; i < destinationSize; i++) { float center = ((i + .5F) * ratio) - .5F; @@ -129,8 +132,8 @@ namespace ImageSharp.Processing.Processors float sum = 0; - result.Weights[i] = result.Slice(i, left, right); - Weights ws = result.Weights[i]; + WeightsWindow ws = result.GetWeightsWindow(i, left, right); + result.Weights[i] = ws; float* weights = ws.Ptr; @@ -154,56 +157,5 @@ namespace ImageSharp.Processing.Processors return result; } - /// - /// Represents a collection of weights and their sum. - /// - protected unsafe struct Weights - { - /// - /// The local left index position - /// - public int Left; - - public BufferSpan Span; - - public float* Ptr => (float*)Span.PointerAtOffset; - - public int Length => Span.Length; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private Weights(int left, BufferSpan span) - { - this.Left = left; - this.Span = span; - } - - internal unsafe class Buffer : IDisposable - { - private PinnedImageBuffer dataBuffer; - - public Weights[] Weights { get; } - - public float* DataPtr { get; } - - internal Weights Slice(int i, int leftIdx, int rightIdx) - { - var span = dataBuffer.GetRowSpan(i).Slice(leftIdx, rightIdx - leftIdx); - return new Weights(leftIdx, span); - } - - public Buffer(int sourceSize, int destinationSize) - { - this.dataBuffer = new PinnedImageBuffer(sourceSize, destinationSize); - this.dataBuffer.Clear(); - this.DataPtr = (float*)this.dataBuffer.Pointer; - this.Weights = new Weights[destinationSize]; - } - - public void Dispose() - { - this.dataBuffer.Dispose(); - } - } - } } } \ 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 39b739134d..77b3f3b6fc 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs @@ -104,6 +104,8 @@ 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()) @@ -128,23 +130,8 @@ namespace ImageSharp.Processing.Processors for (int x = minX; x < maxX; x++) { - // Ensure offsets are normalised for cropping and padding. - - Weights ws = this.HorizontalWeights.Weights[x - startX]; - float* horizontalValues = ws.Ptr; - int left = ws.Left; - - // Destination color components - Vector4 destination = Vector4.Zero; - - for (int i = 0; i < ws.Length; i++) - { - float xw = horizontalValues[i]; - int index = left + i; - destination += tempRowBuffer[index] * xw; - } - - firstPassPixels[x, y] = destination; + WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; + firstPassPixels[x, y] = window.ComputeWeightedRowSum(tempRowBuffer); } } }); @@ -157,21 +144,12 @@ namespace ImageSharp.Processing.Processors y => { // Ensure offsets are normalised for cropping and padding. - Weights ws = this.VerticalWeights.Weights[y - startY]; - float* verticalValues = ws.Ptr; - int left = ws.Left; + WeightsWindow window = this.VerticalWeights.Weights[y - startY]; for (int x = 0; x < width; x++) { // Destination color components - Vector4 destination = Vector4.Zero; - - for (int i = 0; i < ws.Length; i++) - { - float yw = verticalValues[i]; - int index = left + i; - destination += firstPassPixels[x, index] * yw; - } + Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x); TColor d = default(TColor); d.PackFromVector4(destination); From a0a67ddf3591424251c14f7b40bc530eb806dc52 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 18 Mar 2017 19:47:22 +0100 Subject: [PATCH 38/83] merged ResizeProcessor and CompandingResizeProcessor --- .../Transforms/CompandingResizeProcessor.cs | 177 ------------------ .../ResamplingWeightedProcessor.Weights.cs | 24 +++ .../Transforms/ResamplingWeightedProcessor.cs | 1 - .../Processors/Transforms/ResizeProcessor.cs | 48 +++-- .../Processing/Transforms/Resize.cs | 12 +- .../Processors/Filters/ResizeTests.cs | 26 +++ 6 files changed, 88 insertions(+), 200 deletions(-) delete mode 100644 src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs deleted file mode 100644 index c8a43b3d98..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs +++ /dev/null @@ -1,177 +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. - internal 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 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) - { - 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. - WeightsWindow ws = this.HorizontalWeights.Weights[x - startX]; - float* horizontalValues = ws.Ptr; - int left = ws.Left; - - // Destination color components - Vector4 destination = Vector4.Zero; - - for (int i = 0; i < ws.Length; i++) - { - float xw = horizontalValues[i]; - int index = left + i; - destination += sourcePixels[index, y].ToVector4().Expand() * xw; - } - - 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. - WeightsWindow ws = this.VerticalWeights.Weights[y - startY]; - float* verticalValues = ws.Ptr; - int left = ws.Left; - - for (int x = 0; x < width; x++) - { - // Destination color components - Vector4 destination = Vector4.Zero; - - for (int i = 0; i < ws.Length; i++) - { - float yw = verticalValues[i]; - int index = left + i; - destination += firstPassPixels[x, index].ToVector4().Expand() * yw; - } - - 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/ResamplingWeightedProcessor.Weights.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs index 00a81100d2..c7386487ad 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs @@ -69,6 +69,30 @@ namespace ImageSharp.Processing.Processors 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 + public Vector4 ComputeExpandedWeightedRowSum(BufferSpan rowSpan) + { + float* horizontalValues = this.Ptr; + int left = this.Left; + + // Destination color components + Vector4 result = Vector4.Zero; + + for (int i = 0; i < this.Length; i++) + { + float xw = horizontalValues[i]; + int index = left + i; + result += rowSpan[index].Expand() * xw; + } + + return result; + } + /// /// Computes the sum of vectors in 'firstPassPixels' at a column pointed by 'x', /// weighted by weight values, pointed by this instance. diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs index f74bf7edd2..d1a709384c 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs @@ -156,6 +156,5 @@ namespace ImageSharp.Processing.Processors return result; } - } } \ 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 77b3f3b6fc..944e245ac8 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs @@ -12,9 +12,6 @@ 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. internal class ResizeProcessor : ResamplingWeightedProcessor where TColor : struct, IPixel @@ -123,15 +120,27 @@ namespace ImageSharp.Processing.Processors using (PinnedBuffer tempRowBuffer = new PinnedBuffer(sourcePixels.Width)) { BufferSpan sourceRow = sourcePixels.GetRowSpan(y); + BulkPixelOperations.Instance.ToVector4( sourceRow, tempRowBuffer, sourceRow.Length); - for (int x = minX; x < maxX; x++) + if (this.Compand) + { + for (int x = minX; x < maxX; x++) + { + WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; + firstPassPixels[x, y] = window.ComputeExpandedWeightedRowSum(tempRowBuffer); + } + } + else { - WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; - firstPassPixels[x, y] = window.ComputeWeightedRowSum(tempRowBuffer); + for (int x = minX; x < maxX; x++) + { + WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; + firstPassPixels[x, y] = window.ComputeWeightedRowSum(tempRowBuffer); + } } } }); @@ -146,14 +155,29 @@ namespace ImageSharp.Processing.Processors // Ensure offsets are normalised for cropping and padding. WeightsWindow window = this.VerticalWeights.Weights[y - startY]; - for (int x = 0; x < width; x++) + if (this.Compand) { - // Destination color components - Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x); + for (int x = 0; x < width; x++) + { + // 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/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/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs b/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs index 643033f4c4..35994e0285 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs @@ -8,6 +8,32 @@ namespace ImageSharp.Tests using System.IO; using Processing; using Xunit; + using Xunit.Abstractions; + + public class ResizeProfilingBenchmarks : MeasureFixture + { + public ResizeProfilingBenchmarks(ITestOutputHelper output) + : base(output) + { + } + + public int ExecutionCount { get; set; } = 50; + + [Theory] + [InlineData(100, 100)] + [InlineData(1000, 1000)] + public void ResizeBicubic(int width, int height) + { + this.Measure(this.ExecutionCount, + () => + { + using (Image image = new Image(width, height)) + { + image.Resize(width / 4, height / 4); + } + }); + } + } public class ResizeTests : FileTestBase { From cd4fb809cecc84b4f3f91af365677e2644925ebb Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 18 Mar 2017 20:16:17 +0100 Subject: [PATCH 39/83] improved, sequential ResizeProcessor --- .../Common/Memory/PinnedImageBuffer{T}.cs | 9 +- .../ResamplingWeightedProcessor.Weights.cs | 19 +++- .../Processors/Transforms/ResizeProcessor.cs | 104 ++++++++---------- tests/ImageSharp.Sandbox46/Program.cs | 9 +- .../Processors/Filters/ResizeTests.cs | 2 +- 5 files changed, 76 insertions(+), 67 deletions(-) diff --git a/src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs b/src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs index 380545d70f..3ff174c5dd 100644 --- a/src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs +++ b/src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs @@ -53,7 +53,14 @@ namespace ImageSharp /// The x coordinate (row) /// The y coordinate (position at row) /// A reference to the element. - public ref T this[int x, int y] => ref this.Array[(this.Width * y) + x]; + 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)'. diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs index c7386487ad..785a1f20c8 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs @@ -51,19 +51,22 @@ namespace ImageSharp.Processing.Processors /// /// 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 xw = horizontalValues[i]; - int index = left + i; - result += rowSpan[index] * xw; + float weight = horizontalValues[i]; + result += (*vecPtr) * weight; + vecPtr++; } return result; @@ -75,19 +78,22 @@ namespace ImageSharp.Processing.Processors /// /// 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 xw = horizontalValues[i]; - int index = left + i; - result += rowSpan[index].Expand() * xw; + float weight = horizontalValues[i]; + result += (*vecPtr).Expand() * weight; + vecPtr++; } return result; @@ -100,6 +106,7 @@ namespace ImageSharp.Processing.Processors /// 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; diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs index 944e245ac8..0e1a215e0c 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs @@ -110,76 +110,64 @@ namespace ImageSharp.Processing.Processors { firstPassPixels.Clear(); - Parallel.For( - 0, - sourceRectangle.Bottom, - this.ParallelOptions, - y => - { - // TODO: Without Parallel.For() this buffer object could be reused: - using (PinnedBuffer tempRowBuffer = new PinnedBuffer(sourcePixels.Width)) - { - 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); - } - } - } - }); - - // Now process the rows. - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => + using (PinnedBuffer tempRowBuffer = new PinnedBuffer(sourcePixels.Width)) + { + for (int y = 0; y < sourceRectangle.Bottom; y++) { - // Ensure offsets are normalised for cropping and padding. - WeightsWindow window = this.VerticalWeights.Weights[y - startY]; + BufferSpan sourceRow = sourcePixels.GetRowSpan(y); + + BulkPixelOperations.Instance.ToVector4(sourceRow, tempRowBuffer, sourceRow.Length); if (this.Compand) { - for (int x = 0; x < width; x++) + for (int x = minX; x < maxX; x++) { - // Destination color components - Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x); - destination = destination.Compress(); - TColor d = default(TColor); - d.PackFromVector4(destination); - targetPixels[x, y] = d; + WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; + firstPassPixels[x, y] = window.ComputeExpandedWeightedRowSum(tempRowBuffer); } } else { - for (int x = 0; x < width; x++) + for (int x = minX; x < maxX; x++) { - // Destination color components - Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x); - - TColor d = default(TColor); - d.PackFromVector4(destination); - targetPixels[x, y] = d; + WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; + firstPassPixels[x, y] = window.ComputeWeightedRowSum(tempRowBuffer); } } - }); + } + } + + // Now process the rows. + for (int y = minY; y < maxY; y++) + { + // Ensure offsets are normalised for cropping and padding. + WeightsWindow window = this.VerticalWeights.Weights[y - startY]; + + if (this.Compand) + { + for (int x = 0; x < width; x++) + { + // 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; + } + } + } } source.SwapPixelsBuffers(targetPixels); 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/Processors/Filters/ResizeTests.cs b/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs index 35994e0285..a95b196b7e 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs @@ -21,7 +21,7 @@ namespace ImageSharp.Tests [Theory] [InlineData(100, 100)] - [InlineData(1000, 1000)] + [InlineData(2000, 2000)] public void ResizeBicubic(int width, int height) { this.Measure(this.ExecutionCount, From 7ced8f07c6fa8a468fb052f34a340d3d125d09de Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 18 Mar 2017 20:27:27 +0100 Subject: [PATCH 40/83] make it parallel again! --- .../Processors/Transforms/ResizeProcessor.cs | 104 ++++++++++-------- 1 file changed, 58 insertions(+), 46 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs index 0e1a215e0c..944e245ac8 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs @@ -110,64 +110,76 @@ namespace ImageSharp.Processing.Processors { firstPassPixels.Clear(); - using (PinnedBuffer tempRowBuffer = new PinnedBuffer(sourcePixels.Width)) - { - for (int y = 0; y < sourceRectangle.Bottom; y++) - { - BufferSpan sourceRow = sourcePixels.GetRowSpan(y); + Parallel.For( + 0, + sourceRectangle.Bottom, + this.ParallelOptions, + y => + { + // TODO: Without Parallel.For() this buffer object could be reused: + using (PinnedBuffer tempRowBuffer = new PinnedBuffer(sourcePixels.Width)) + { + 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); + } + } + } + }); - BulkPixelOperations.Instance.ToVector4(sourceRow, tempRowBuffer, sourceRow.Length); + // Now process the rows. + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + // Ensure offsets are normalised for cropping and padding. + WeightsWindow window = this.VerticalWeights.Weights[y - startY]; if (this.Compand) { - for (int x = minX; x < maxX; x++) + for (int x = 0; x < width; x++) { - WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; - firstPassPixels[x, y] = window.ComputeExpandedWeightedRowSum(tempRowBuffer); + // 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 = minX; x < maxX; x++) + for (int x = 0; x < width; x++) { - WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; - firstPassPixels[x, y] = window.ComputeWeightedRowSum(tempRowBuffer); - } - } - } - } - - // Now process the rows. - for (int y = minY; y < maxY; y++) - { - // Ensure offsets are normalised for cropping and padding. - WeightsWindow window = this.VerticalWeights.Weights[y - startY]; - - if (this.Compand) - { - for (int x = 0; x < width; x++) - { - // 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); + // 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; + } } - } - } + }); } source.SwapPixelsBuffers(targetPixels); From dce492971e0fc410247a5fa51af931226780141d Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 18 Mar 2017 20:40:38 +0100 Subject: [PATCH 41/83] moved ResizeProfilingBenchmarks --- .../Filters/ResizeProfilingBenchmarks.cs | 30 +++++++++++++++++++ .../Processors/Filters/ResizeTests.cs | 26 ---------------- 2 files changed, 30 insertions(+), 26 deletions(-) create mode 100644 tests/ImageSharp.Tests/Processors/Filters/ResizeProfilingBenchmarks.cs diff --git a/tests/ImageSharp.Tests/Processors/Filters/ResizeProfilingBenchmarks.cs b/tests/ImageSharp.Tests/Processors/Filters/ResizeProfilingBenchmarks.cs new file mode 100644 index 0000000000..06cd228a7e --- /dev/null +++ b/tests/ImageSharp.Tests/Processors/Filters/ResizeProfilingBenchmarks.cs @@ -0,0 +1,30 @@ +namespace ImageSharp.Tests +{ + 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); + } + }); + } + } +} \ 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 a95b196b7e..643033f4c4 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs @@ -8,32 +8,6 @@ namespace ImageSharp.Tests using System.IO; using Processing; using Xunit; - using Xunit.Abstractions; - - public class ResizeProfilingBenchmarks : MeasureFixture - { - public ResizeProfilingBenchmarks(ITestOutputHelper output) - : base(output) - { - } - - public int ExecutionCount { get; set; } = 50; - - [Theory] - [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); - } - }); - } - } public class ResizeTests : FileTestBase { From 8f570854f04bc36bdabcf74aa49c07be483ccf0d Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 18 Mar 2017 21:13:58 +0100 Subject: [PATCH 42/83] fixed PinnedImageBufferTests.GetRowSpanY() --- tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs b/tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs index 7756b0aa98..a23f93a70b 100644 --- a/tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs +++ b/tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs @@ -65,7 +65,7 @@ namespace ImageSharp.Tests.Common Assert.Equal(width * y, span.Start); Assert.Equal(width, span.Length); - Assert.Equal(buffer.Pointer + sizeof(int) * width * y, span.PointerAtOffset); + Assert.Equal(buffer.Pointer + sizeof(Foo) * width * y, span.PointerAtOffset); } } @@ -81,7 +81,7 @@ namespace ImageSharp.Tests.Common Assert.Equal(width * y + x, span.Start); Assert.Equal(width - x, span.Length); - Assert.Equal(buffer.Pointer + sizeof(int) * (width * y + x), span.PointerAtOffset); + Assert.Equal(buffer.Pointer + sizeof(Foo) * (width * y + x), span.PointerAtOffset); } } From 36f9510031569b707893229cfa992681392cad47 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 19 Mar 2017 01:55:28 +0100 Subject: [PATCH 43/83] IterateArray benchmark --- .../General/IterateArray.cs | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 tests/ImageSharp.Benchmarks/General/IterateArray.cs 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 From b37be1d81ff138e8b6622f9eb6e32329ad1f5ae4 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sun, 19 Mar 2017 12:32:52 +0100 Subject: [PATCH 44/83] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 383cc11f87..576ed76d07 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [![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) From f15bf5b872d9664e10843f4fcb9fd5aac2bc7d71 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 19 Mar 2017 15:36:59 +0000 Subject: [PATCH 45/83] fix symbols server url --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 89cb576539..6b7ba946ec 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -20,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/ From cd1e7b3b97d55de4647ed789be9015f30a497e81 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 21 Mar 2017 03:40:32 +0100 Subject: [PATCH 46/83] weight printing experiments, minor changes, comments --- .../ResamplingWeightedProcessor.Weights.cs | 9 ++-- .../Transforms/ResamplingWeightedProcessor.cs | 53 ++++++++++--------- .../Filters/ResizeProfilingBenchmarks.cs | 31 +++++++++++ 3 files changed, 63 insertions(+), 30 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs index 785a1f20c8..24d898fee9 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing.Processors /// /// Points to a collection of of weights allocated in . /// - protected unsafe struct WeightsWindow + internal unsafe struct WeightsWindow { /// /// The local left index position @@ -22,6 +22,8 @@ namespace ImageSharp.Processing.Processors /// /// 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; /// @@ -129,7 +131,7 @@ namespace ImageSharp.Processing.Processors /// /// Holds the values in an optimized contigous memory region. /// - protected class WeightsBuffer : IDisposable + internal class WeightsBuffer : IDisposable { private PinnedImageBuffer dataBuffer; @@ -140,8 +142,7 @@ namespace ImageSharp.Processing.Processors /// The size of the destination window public WeightsBuffer(int sourceSize, int destinationSize) { - this.dataBuffer = new PinnedImageBuffer(sourceSize, destinationSize); - this.dataBuffer.Clear(); + this.dataBuffer = PinnedImageBuffer.CreateClean(sourceSize, destinationSize); this.Weights = new WeightsWindow[destinationSize]; } diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs index d1a709384c..255124a754 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs @@ -68,38 +68,14 @@ namespace ImageSharp.Processing.Processors /// protected WeightsBuffer 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 override void AfterApply(ImageBase source, Rectangle sourceRectangle) - { - base.AfterApply(source, sourceRectangle); - this.HorizontalWeights?.Dispose(); - this.HorizontalWeights = null; - this.VerticalWeights?.Dispose(); - this.VerticalWeights = null; - } - /// /// Computes the weights to apply at each pixel when resizing. /// /// The destination size /// The source size /// The - protected unsafe WeightsBuffer PrecomputeWeights(int destinationSize, int sourceSize) + // 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; @@ -156,5 +132,30 @@ namespace ImageSharp.Processing.Processors return result; } + + /// + 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 override void AfterApply(ImageBase source, Rectangle sourceRectangle) + { + 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/tests/ImageSharp.Tests/Processors/Filters/ResizeProfilingBenchmarks.cs b/tests/ImageSharp.Tests/Processors/Filters/ResizeProfilingBenchmarks.cs index 06cd228a7e..da09aa85e7 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/ResizeProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/ResizeProfilingBenchmarks.cs @@ -1,5 +1,11 @@ namespace ImageSharp.Tests { + using System.IO; + using System.Text; + + using ImageSharp.Processing; + using ImageSharp.Processing.Processors; + using Xunit; using Xunit.Abstractions; @@ -26,5 +32,30 @@ namespace ImageSharp.Tests } }); } + + // [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 From a3185fd6e787f9d0479de3d38dec04bd773cab85 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Mon, 20 Mar 2017 17:27:01 +0000 Subject: [PATCH 47/83] Move image loading out of constructors and into static methods --- ImageSharp.sln | 2 +- src/ImageSharp/Formats/Bmp/BmpDecoder.cs | 5 +- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 15 +- src/ImageSharp/Formats/Gif/GifDecoder.cs | 10 +- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 48 ++- src/ImageSharp/Formats/IImageDecoder.cs | 4 +- src/ImageSharp/Formats/Jpeg/JpegDecoder.cs | 5 +- .../Formats/Jpeg/JpegDecoderCore.cs | 120 +++--- src/ImageSharp/Formats/Png/PngDecoder.cs | 10 +- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 39 +- src/ImageSharp/Image.Decode.cs | 67 ++++ src/ImageSharp/Image.FromBytes.cs | 148 ++++++++ src/ImageSharp/Image.FromFile.cs | 148 ++++++++ src/ImageSharp/Image.FromStream.cs | 184 ++++++++++ src/ImageSharp/Image.cs | 190 +--------- src/ImageSharp/Image/IImageBase.cs | 10 - src/ImageSharp/Image/IImageBase{TColor}.cs | 10 - src/ImageSharp/Image/ImageBase{TColor}.cs | 41 +-- src/ImageSharp/Image/Image{TColor}.cs | 296 +-------------- src/ImageSharp/MetaData/ImageMetaData.cs | 44 ++- .../MetaData/Profiles/Exif/ExifProfile.cs | 2 +- .../ImageSharp.Benchmarks/Image/DecodeBmp.cs | 2 +- .../Image/DecodeFilteredPng.cs | 2 +- .../ImageSharp.Benchmarks/Image/DecodeGif.cs | 2 +- .../ImageSharp.Benchmarks/Image/DecodeJpeg.cs | 2 +- .../Image/DecodeJpegMultiple.cs | 2 +- .../ImageSharp.Benchmarks/Image/DecodePng.cs | 2 +- .../ImageSharp.Benchmarks/Image/EncodeBmp.cs | 2 +- .../ImageSharp.Benchmarks/Image/EncodeGif.cs | 2 +- .../Image/EncodeIndexedPng.cs | 2 +- .../ImageSharp.Benchmarks/Image/EncodeJpeg.cs | 2 +- .../ImageSharp.Benchmarks/Image/EncodePng.cs | 2 +- .../Image/MultiImageBenchmarkBase.cs | 2 +- .../Samplers/DetectEdges.cs | 2 +- .../Formats/GeneralFormatTests.cs | 2 +- .../Formats/Gif/GifEncoderTests.cs | 6 +- .../Formats/Jpg/JpegDecoderTests.cs | 5 +- .../Formats/Jpg/JpegEncoderTests.cs | 4 +- .../Formats/Jpg/JpegProfilingBenchmarks.cs | 2 +- .../ImageSharp.Tests/Image/ImageLoadTests.cs | 341 ++++++++++++++++++ tests/ImageSharp.Tests/Image/ImageTests.cs | 10 +- .../Profiles/Exif/ExifProfileTests.cs | 6 +- tests/ImageSharp.Tests/TestFile.cs | 4 +- tests/ImageSharp.Tests/TestFormat.cs | 174 +++++++++ .../TestUtilities/Factories/GenericFactory.cs | 2 +- .../TestUtilities/Factories/ImageFactory.cs | 2 +- 46 files changed, 1293 insertions(+), 689 deletions(-) create mode 100644 src/ImageSharp/Image.Decode.cs create mode 100644 src/ImageSharp/Image.FromBytes.cs create mode 100644 src/ImageSharp/Image.FromFile.cs create mode 100644 src/ImageSharp/Image.FromStream.cs create mode 100644 tests/ImageSharp.Tests/Image/ImageLoadTests.cs create mode 100644 tests/ImageSharp.Tests/TestFormat.cs diff --git a/ImageSharp.sln b/ImageSharp.sln index 9c729493b2..628fa70153 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 diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index 9f490a3a9b..2bc1c8cc30 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -26,13 +26,12 @@ namespace ImageSharp.Formats public class BmpDecoder : IImageDecoder { /// - public void Decode(Image image, Stream stream, IDecoderOptions options) + public Image Decode(Stream stream, IDecoderOptions options) where TColor : struct, IPixel { - Guard.NotNull(image, "image"); Guard.NotNull(stream, "stream"); - new BmpDecoderCore().Decode(image, stream); + return new BmpDecoderCore().Decode(stream); } } } diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index a75031ea19..adfa4b6ace 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -48,16 +48,13 @@ namespace ImageSharp.Formats /// 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,14 +107,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 = new Image(this.infoHeader.Width, this.infoHeader.Height); using (PixelAccessor pixels = image.Lock()) { @@ -151,6 +148,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..16b036e684 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -14,25 +14,25 @@ namespace ImageSharp.Formats public class GifDecoder : IImageDecoder { /// - public void Decode(Image image, Stream stream, IDecoderOptions options) + public Image Decode(Stream stream, IDecoderOptions options) where TColor : struct, IPixel { IGifDecoderOptions gifOptions = GifDecoderOptions.Create(options); - this.Decode(image, stream, gifOptions); + return this.Decode(stream, gifOptions); } /// /// Decodes the image from the specified stream to the . /// /// The pixel format. - /// The to decode to. /// 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(Stream stream, IGifDecoderOptions options) where TColor : struct, IPixel { - new GifDecoderCore(options).Decode(image, stream); + return new GifDecoderCore(options).Decode(stream); } } } diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index ab1edc2c76..22a26345fe 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -27,11 +27,6 @@ namespace ImageSharp.Formats /// private readonly IGifDecoderOptions options; - /// - /// The image to decode the information to. - /// - private Image decodedImage; - /// /// The currently loaded stream. /// @@ -67,6 +62,16 @@ 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. /// @@ -79,13 +84,13 @@ namespace ImageSharp.Formats /// /// 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 +149,8 @@ namespace ImageSharp.Formats ArrayPool.Shared.Return(this.globalColorTable); } } + + return this.image; } /// @@ -212,11 +219,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 +270,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 +352,15 @@ 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 = new Image(imageWidth, imageHeight); + this.image.MetaData.LoadFrom(this.metaData); - this.SetFrameDelay(this.decodedImage.MetaData); + this.SetFrameDelay(this.metaData); - image = this.decodedImage; + image = this.image; } else { @@ -368,7 +378,7 @@ namespace ImageSharp.Formats this.RestoreToBackground(image); - this.decodedImage.Frames.Add(currentFrame); + this.image.Frames.Add(currentFrame); } int i = 0; @@ -441,7 +451,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 +472,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..c4a9cf8c3c 100644 --- a/src/ImageSharp/Formats/IImageDecoder.cs +++ b/src/ImageSharp/Formats/IImageDecoder.cs @@ -17,10 +17,10 @@ namespace ImageSharp.Formats /// Decodes the image from the specified stream to the . /// /// The pixel format. - /// The to decode to. /// The containing image data. /// The options for the decoder. - void Decode(Image image, Stream stream, IDecoderOptions options) + /// The decoded image + Image Decode(Stream stream, IDecoderOptions options) where TColor : struct, IPixel; } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index eeb371d1e7..3a91f8010e 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(Stream stream, IDecoderOptions options) where TColor : struct, IPixel { - Guard.NotNull(image, "image"); Guard.NotNull(stream, "stream"); using (JpegDecoderCore decoder = new JpegDecoderCore(options)) { - 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..fa656e71e8 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -180,18 +180,30 @@ 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; + } + + /// + /// Decodes the image from the specified and sets + /// the data to image. + /// + /// The stream, where the image should be. + /// The image metadata. + public ImageMetaData DecodeMetaData(Stream stream) + { + ImageMetaData metadata = new ImageMetaData(); + this.ProcessStream(metadata, stream, true); + return metadata; } /// @@ -276,12 +288,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 +439,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 +506,18 @@ 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 = new Image(this.ImageWidth, this.ImageHeight); + image.MetaData.LoadFrom(metadata); + if (this.grayImage.IsInitialized) { - this.ConvertFromGrayScale(this.ImageWidth, this.ImageHeight, image); + this.ConvertFromGrayScale(image); + return image; } else if (this.ycbcrImage != null) { @@ -519,27 +534,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 +597,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 +634,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 +665,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 +683,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 +703,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 +720,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 +740,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 +955,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 +972,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..5b7d97fc70 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -31,25 +31,25 @@ namespace ImageSharp.Formats public class PngDecoder : IImageDecoder { /// - public void Decode(Image image, Stream stream, IDecoderOptions options) + public Image Decode(Stream stream, IDecoderOptions options) where TColor : struct, IPixel { IPngDecoderOptions pngOptions = PngDecoderOptions.Create(options); - this.Decode(image, stream, pngOptions); + return this.Decode(stream, pngOptions); } /// /// Decodes the image from the specified stream to the . /// /// The pixel format. - /// The to decode to. /// The containing image data. /// The options for the decoder. - public void Decode(Image image, Stream stream, IPngDecoderOptions options) + /// The decoded image. + public Image Decode(Stream stream, IPngDecoderOptions options) where TColor : struct, IPixel { - new PngDecoderCore(options).Decode(image, stream); + return new PngDecoderCore(options).Decode(stream); } } } diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index fd03ed39b8..dadf7ab7d6 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -148,7 +148,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 +155,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 +177,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 +186,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 +194,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 +208,20 @@ 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 = new Image(this.header.Width, this.header.Height); + image.MetaData.LoadFrom(metadata); using (PixelAccessor pixels = image.Lock()) { this.ReadScanlines(dataStream, pixels); } + + return image; } } @@ -270,18 +273,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 +769,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 +793,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/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs new file mode 100644 index 0000000000..f31f2c0a55 --- /dev/null +++ b/src/ImageSharp/Image.Decode.cs @@ -0,0 +1,67 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Buffers; + using System.Diagnostics; + using System.IO; + using System.Linq; + 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 + { + /// + /// Decodes the image stream to the current image. + /// + /// The pixel format. + /// The stream. + /// The options for the decoder. + /// the configuration. + /// The decoded image + /// + /// [true] if can successfull decode the image otherwise [false]. + /// + private static bool Decode(Stream stream, IDecoderOptions options, Configuration config, out Image img) + where TColor : struct, IPixel + { + img = null; + int maxHeaderSize = config.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 = config.ImageFormats.FirstOrDefault(x => x.IsSupportedFileFormat(header)); + } + finally + { + ArrayPool.Shared.Return(header); + } + + if (format == null) + { + return false; + } + + img = format.Decoder.Decode(stream, options); + img.CurrentImageFormat = format; + return true; + } + } +} diff --git a/src/ImageSharp/Image.FromBytes.cs b/src/ImageSharp/Image.FromBytes.cs new file mode 100644 index 0000000000..e68a57fe70 --- /dev/null +++ b/src/ImageSharp/Image.FromBytes.cs @@ -0,0 +1,148 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Buffers; + using System.Diagnostics; + using System.IO; + using System.Linq; + 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(byte[] stream) + { + return Load(stream, null, 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(byte[] stream, IDecoderOptions options) + { + return Load(stream, options, null); + } + + /// + /// Loads the image from the given stream. + /// + /// The stream containing image information. + /// The config for the decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(byte[] stream, Configuration config) + { + return Load(stream, null, config); + } + + /// + /// Loads the image from the given stream. + /// + /// The stream containing image information. + /// The options for the decoder. + /// The configuration options. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(byte[] stream, IDecoderOptions options, Configuration config) + { + using (MemoryStream ms = new MemoryStream(stream)) + { + return Load(ms, options, config); + } + } + + /// + /// 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(byte[] stream) + where TColor : struct, IPixel + { + return Load(stream, null, 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(byte[] stream, IDecoderOptions options) + where TColor : struct, IPixel + { + return Load(stream, options, null); + } + + /// + /// Loads the image from the given stream. + /// + /// The pixel format. + /// The stream containing image information. + /// The config for the decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(byte[] stream, Configuration config) + where TColor : struct, IPixel + { + return Load(stream, null, config); + } + + /// + /// Loads the image from the given stream. + /// + /// The pixel format. + /// The stream containing image information. + /// The options for the decoder. + /// The configuration options. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(byte[] stream, IDecoderOptions options, Configuration config) + where TColor : struct, IPixel + { + using (MemoryStream ms = new MemoryStream(stream)) + { + return Load(ms, options, config); + } + } + } +} diff --git a/src/ImageSharp/Image.FromFile.cs b/src/ImageSharp/Image.FromFile.cs new file mode 100644 index 0000000000..b0adb1f96f --- /dev/null +++ b/src/ImageSharp/Image.FromFile.cs @@ -0,0 +1,148 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Buffers; + using System.Diagnostics; + using System.IO; + using System.Linq; + using System.Text; + using Formats; + +#if !NETSTANDARD1_1 + /// + /// 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(string stream) + { + return Load(stream, null, 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(string stream, IDecoderOptions options) + { + return Load(stream, options, null); + } + + /// + /// Loads the image from the given stream. + /// + /// The stream containing image information. + /// The config for the decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(string stream, Configuration config) + { + return Load(stream, null, config); + } + + /// + /// Loads the image from the given stream. + /// + /// The stream containing image information. + /// The options for the decoder. + /// The configuration options. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(string stream, IDecoderOptions options, Configuration config) + { + return new Image(Image.Load(stream, options, config)); + } + + /// + /// 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(string stream) + where TColor : struct, IPixel + { + return Load(stream, null, 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(string stream, IDecoderOptions options) + where TColor : struct, IPixel + { + return Load(stream, options, null); + } + + /// + /// Loads the image from the given stream. + /// + /// The pixel format. + /// The stream containing image information. + /// The config for the decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(string stream, Configuration config) + where TColor : struct, IPixel + { + return Load(stream, null, config); + } + + /// + /// Loads the image from the given stream. + /// + /// The pixel format. + /// The stream containing image information. + /// The options for the decoder. + /// The configuration options. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(string stream, IDecoderOptions options, Configuration config) + where TColor : struct, IPixel + { + config = config ?? Configuration.Default; + using (Stream s = config.FileSystem.OpenRead(stream)) + { + return Load(s, options, config); + } + } + } +#endif +} diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs new file mode 100644 index 0000000000..36309db5b5 --- /dev/null +++ b/src/ImageSharp/Image.FromStream.cs @@ -0,0 +1,184 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Buffers; + using System.Diagnostics; + using System.IO; + using System.Linq; + 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(stream, null, 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(stream, options, null); + } + + /// + /// Loads the image from the given stream. + /// + /// The stream containing image information. + /// The config for the decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(Stream stream, Configuration config) + { + return Load(stream, null, config); + } + + /// + /// Loads the image from the given stream. + /// + /// The stream containing image information. + /// The options for the decoder. + /// The configuration options. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(Stream stream, IDecoderOptions options, Configuration config) + { + return new Image(Load(stream, options, config)); + } + + /// + /// 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(stream, null, 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(stream, options, null); + } + + /// + /// Loads the image from the given stream. + /// + /// The pixel format. + /// The stream containing image information. + /// The config for the decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(Stream stream, Configuration config) + where TColor : struct, IPixel + { + return Load(stream, null, config); + } + + /// + /// Loads the image from the given stream. + /// + /// The pixel format. + /// The stream containing image information. + /// The options for the decoder. + /// The configuration options. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(Stream stream, IDecoderOptions options, Configuration config) + where TColor : struct, IPixel + { + config = config ?? Configuration.Default; + + if (!config.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 (Decode(stream, options, config, out Image img)) + { + return img; + } + } + 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 (Decode(ms, options, config, out Image img)) + { + 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()); + } + } +} diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs index 8bfd8ee1a3..352107021a 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,190 +27,19 @@ 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. - /// - /// - /// 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) - { - } - - /// - /// 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) - : base(bytes, null, 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(bytes, options, 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) { } @@ -219,7 +49,7 @@ namespace ImageSharp /// /// The other image, where the clone should be made from. /// is null. - public Image(Image other) + internal Image(Image other) : base(other) { } 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 34724cc977..82991948cb 100644 --- a/src/ImageSharp/Image/Image{TColor}.cs +++ b/src/ImageSharp/Image/Image{TColor}.cs @@ -35,7 +35,7 @@ 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) { if (!this.Configuration.ImageFormats.Any()) @@ -43,203 +43,19 @@ namespace ImageSharp throw new InvalidOperationException("No image formats have been configured."); } + this.MetaData = new ImageMetaData(); 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 (Stream fs = this.Configuration.FileSystem.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) - { - } - - /// - /// 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); - } } /// @@ -271,7 +87,6 @@ namespace ImageSharp public Image(ImageBase other) : base(other) { - this.CopyProperties(other); } /// @@ -588,103 +403,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/MetaData/ImageMetaData.cs b/src/ImageSharp/MetaData/ImageMetaData.cs index de1e424769..d7d5e88b5f 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; /// @@ -47,21 +48,7 @@ namespace ImageSharp { DebugGuard.NotNull(other, nameof(other)); - this.HorizontalResolution = other.HorizontalResolution; - this.VerticalResolution = other.VerticalResolution; - this.Quality = other.Quality; - this.FrameDelay = other.FrameDelay; - this.RepeatCount = other.RepeatCount; - - foreach (ImageProperty property in other.Properties) - { - this.Properties.Add(new ImageProperty(property)); - } - - if (other.ExifProfile != null) - { - this.ExifProfile = new ExifProfile(other.ExifProfile); - } + this.LoadFrom(other); } /// @@ -143,5 +130,32 @@ namespace ImageSharp { this.ExifProfile?.Sync(this); } + + /// + /// Sets the current metadata values based on a previous metadata object. + /// + /// Meta data object to copy values from. + internal void LoadFrom(ImageMetaData other) + { + this.HorizontalResolution = other.HorizontalResolution; + this.VerticalResolution = other.VerticalResolution; + this.Quality = other.Quality; + this.FrameDelay = other.FrameDelay; + this.RepeatCount = other.RepeatCount; + + foreach (ImageProperty property in other.Properties) + { + this.Properties.Add(new ImageProperty(property)); + } + + if (other.ExifProfile != null) + { + 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/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 index ba383873c2..1318c1674a 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs +++ b/tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs @@ -34,7 +34,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 Image(this.bmpStream); + this.bmpCore = Image.Load(this.bmpStream); this.bmpStream.Position = 0; } } 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 4fee634f5f..a084ca025c 100644 --- a/tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs +++ b/tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs @@ -154,7 +154,7 @@ namespace ImageSharp.Benchmarks.Image using (MemoryStream ms1 = new MemoryStream(bytes)) { - this.FileNamesToImageSharpImages[fn] = new Image(ms1); + this.FileNamesToImageSharpImages[fn] = Image.Load(ms1); } 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.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/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index a140b7a3cf..897778bc3a 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -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); @@ -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/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 5723f9b234..416c88a500 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)) { - decoder.Decode(mirror, ms, true); + Image mirror = decoder.Decode(ms); Assert.Equal(decoder.ImageWidth, image.Width); Assert.Equal(decoder.ImageHeight, image.Height); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index c97eb14619..0833cb8680 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -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); } @@ -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/Image/ImageLoadTests.cs b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs new file mode 100644 index 0000000000..2c97ea06fd --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs @@ -0,0 +1,341 @@ +// +// 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())) + .Callback((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); + } + + [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); + } + + [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); + } + + [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); + } + + [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); + } + + [Fact] + public void LoadFromStreamWithConfig() + { + Stream stream = new MemoryStream(); + Image img = Image.Load(stream, this.LocalConfiguration); + + Assert.NotNull(img); + Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); + this.localDecoder.Verify(x => x.Decode(stream, null)); + } + + [Fact] + public void LoadFromStreamWithTypeAndConfig() + { + Stream stream = new MemoryStream(); + Image img = Image.Load(stream, this.LocalConfiguration); + + Assert.NotNull(img); + Assert.Equal(this.returnImage, img); + Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); + this.localDecoder.Verify(x => x.Decode(stream, null)); + } + + [Fact] + public void LoadFromStreamWithConfigAndOptions() + { + Stream stream = new MemoryStream(); + Image img = Image.Load(stream, this.decoderOptions, this.LocalConfiguration); + + Assert.NotNull(img); + Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); + this.localDecoder.Verify(x => x.Decode(stream, this.decoderOptions)); + } + + [Fact] + public void LoadFromStreamWithTypeAndConfigAndOptions() + { + Stream stream = new MemoryStream(); + Image img = Image.Load(stream, this.decoderOptions, this.LocalConfiguration); + + Assert.NotNull(img); + Assert.Equal(this.returnImage, img); + Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); + this.localDecoder.Verify(x => x.Decode(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); + } + + [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); + } + + [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); + } + + [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); + } + + [Fact] + public void LoadFromBytesWithConfig() + { + Image img = Image.Load(this.DataStream.ToArray(), this.LocalConfiguration); + + Assert.NotNull(img); + Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); + this.localDecoder.Verify(x => x.Decode(It.IsAny(), null)); + Assert.Equal(this.DataStream.ToArray(), this.DecodedData); + } + + [Fact] + public void LoadFromBytesWithTypeAndConfig() + { + Image img = Image.Load(this.DataStream.ToArray(), this.LocalConfiguration); + + Assert.NotNull(img); + Assert.Equal(this.returnImage, img); + Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); + + this.localDecoder.Verify(x => x.Decode(It.IsAny(), null)); + Assert.Equal(this.DataStream.ToArray(), this.DecodedData); + } + + [Fact] + public void LoadFromBytesWithConfigAndOptions() + { + Image img = Image.Load(this.DataStream.ToArray(), this.decoderOptions, this.LocalConfiguration); + + Assert.NotNull(img); + Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); + this.localDecoder.Verify(x => x.Decode(It.IsAny(), this.decoderOptions)); + Assert.Equal(this.DataStream.ToArray(), this.DecodedData); + } + + [Fact] + public void LoadFromBytesWithTypeAndConfigAndOptions() + { + Image img = Image.Load(this.DataStream.ToArray(), this.decoderOptions, this.LocalConfiguration); + + Assert.NotNull(img); + Assert.Equal(this.returnImage, img); + Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); + this.localDecoder.Verify(x => x.Decode(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); + } + + [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); + } + + [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); + } + + [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); + } + + [Fact] + public void LoadFromFileWithConfig() + { + Image img = Image.Load(this.FilePath, this.LocalConfiguration); + + Assert.NotNull(img); + Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); + this.localDecoder.Verify(x => x.Decode(this.DataStream, null)); + } + + [Fact] + public void LoadFromFileWithTypeAndConfig() + { + Image img = Image.Load(this.FilePath, this.LocalConfiguration); + + Assert.NotNull(img); + Assert.Equal(this.returnImage, img); + Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); + this.localDecoder.Verify(x => x.Decode(this.DataStream, null)); + } + + [Fact] + public void LoadFromFileWithConfigAndOptions() + { + Image img = Image.Load(this.FilePath, this.decoderOptions, this.LocalConfiguration); + + Assert.NotNull(img); + Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); + this.localDecoder.Verify(x => x.Decode(this.DataStream, this.decoderOptions)); + } + + [Fact] + public void LoadFromFileWithTypeAndConfigAndOptions() + { + Image img = Image.Load(FilePath, this.decoderOptions, this.LocalConfiguration); + + Assert.NotNull(img); + Assert.Equal(this.returnImage, img); + Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); + this.localDecoder.Verify(x => x.Decode(this.DataStream, this.decoderOptions)); + } + + public void Dispose() + { + // clean up the global object; + this.returnImage?.Dispose(); + } + } +} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index b0a031a780..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); }); } diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs index 785d9dcfc2..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); @@ -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/TestFile.cs b/tests/ImageSharp.Tests/TestFile.cs index 701025e871..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); } /// diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs new file mode 100644 index 0000000000..3a40ed4201 --- /dev/null +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -0,0 +1,174 @@ +// +// 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) + { + DecodeOperation[] discovered = this.DecodeCalls.Where(x => x.IsMatch(marker, options)).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; + + public bool IsMatch(byte[] testMarker, IDecoderOptions testOptions) + { + if(this.options != testOptions) + { + 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(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 + }); + + // 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/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); From 04dd7056648d8fbbb71f2f99dad25e94ce98fb4a Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Tue, 21 Mar 2017 18:37:45 +0000 Subject: [PATCH 48/83] fix parameter names --- src/ImageSharp/Image.FromBytes.cs | 64 +++++++++++++++---------------- src/ImageSharp/Image.FromFile.cs | 64 +++++++++++++++---------------- 2 files changed, 64 insertions(+), 64 deletions(-) diff --git a/src/ImageSharp/Image.FromBytes.cs b/src/ImageSharp/Image.FromBytes.cs index e68a57fe70..3f1a3031c0 100644 --- a/src/ImageSharp/Image.FromBytes.cs +++ b/src/ImageSharp/Image.FromBytes.cs @@ -20,126 +20,126 @@ namespace ImageSharp public sealed partial class Image { /// - /// Loads the image from the given stream. + /// Loads the image from the given byte array. /// - /// The stream containing image information. + /// The byte array containing image data. /// /// Thrown if the stream is not readable nor seekable. /// /// The image - public static Image Load(byte[] stream) + public static Image Load(byte[] data) { - return Load(stream, null, null); + return Load(data, null, null); } /// - /// Loads the image from the given stream. + /// Loads the image from the given byte array. /// - /// The stream containing image information. + /// 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[] stream, IDecoderOptions options) + public static Image Load(byte[] data, IDecoderOptions options) { - return Load(stream, options, null); + return Load(data, options, null); } /// - /// Loads the image from the given stream. + /// Loads the image from the given byte array. /// - /// The stream containing image information. + /// The byte array containing image data. /// The config for the decoder. /// /// Thrown if the stream is not readable nor seekable. /// /// The image - public static Image Load(byte[] stream, Configuration config) + public static Image Load(byte[] data, Configuration config) { - return Load(stream, null, config); + return Load(data, null, config); } /// - /// Loads the image from the given stream. + /// Loads the image from the given byte array. /// - /// The stream containing image information. + /// The byte array containing image data. /// The options for the decoder. /// The configuration options. /// /// Thrown if the stream is not readable nor seekable. /// /// The image - public static Image Load(byte[] stream, IDecoderOptions options, Configuration config) + public static Image Load(byte[] data, IDecoderOptions options, Configuration config) { - using (MemoryStream ms = new MemoryStream(stream)) + using (MemoryStream ms = new MemoryStream(data)) { return Load(ms, options, config); } } /// - /// Loads the image from the given stream. + /// Loads the image from the given byte array. /// /// The pixel format. - /// The stream containing image information. + /// The byte array containing image data. /// /// Thrown if the stream is not readable nor seekable. /// /// The image - public static Image Load(byte[] stream) + public static Image Load(byte[] data) where TColor : struct, IPixel { - return Load(stream, null, null); + return Load(data, null, null); } /// - /// Loads the image from the given stream. + /// Loads the image from the given byte array. /// /// The pixel format. - /// The stream containing image information. + /// 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[] stream, IDecoderOptions options) + public static Image Load(byte[] data, IDecoderOptions options) where TColor : struct, IPixel { - return Load(stream, options, null); + return Load(data, options, null); } /// - /// Loads the image from the given stream. + /// Loads the image from the given byte array. /// /// The pixel format. - /// The stream containing image information. + /// The byte array containing image data. /// The config for the decoder. /// /// Thrown if the stream is not readable nor seekable. /// /// The image - public static Image Load(byte[] stream, Configuration config) + public static Image Load(byte[] data, Configuration config) where TColor : struct, IPixel { - return Load(stream, null, config); + return Load(data, null, config); } /// - /// Loads the image from the given stream. + /// Loads the image from the given byte array. /// /// The pixel format. - /// The stream containing image information. + /// The byte array containing image data. /// The options for the decoder. /// The configuration options. /// /// Thrown if the stream is not readable nor seekable. /// /// The image - public static Image Load(byte[] stream, IDecoderOptions options, Configuration config) + public static Image Load(byte[] data, IDecoderOptions options, Configuration config) where TColor : struct, IPixel { - using (MemoryStream ms = new MemoryStream(stream)) + using (MemoryStream ms = new MemoryStream(data)) { return Load(ms, options, config); } diff --git a/src/ImageSharp/Image.FromFile.cs b/src/ImageSharp/Image.FromFile.cs index b0adb1f96f..9b8ba83ded 100644 --- a/src/ImageSharp/Image.FromFile.cs +++ b/src/ImageSharp/Image.FromFile.cs @@ -21,124 +21,124 @@ namespace ImageSharp public sealed partial class Image { /// - /// Loads the image from the given stream. + /// Loads the image from the given file. /// - /// The stream containing image information. + /// The file path to the image. /// /// Thrown if the stream is not readable nor seekable. /// /// The image - public static Image Load(string stream) + public static Image Load(string path) { - return Load(stream, null, null); + return Load(path, null, null); } /// - /// Loads the image from the given stream. + /// Loads the image from the given file. /// - /// The stream containing image information. + /// 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 stream, IDecoderOptions options) + public static Image Load(string path, IDecoderOptions options) { - return Load(stream, options, null); + return Load(path, options, null); } /// - /// Loads the image from the given stream. + /// Loads the image from the given file. /// - /// The stream containing image information. + /// The file path to the image. /// The config for the decoder. /// /// Thrown if the stream is not readable nor seekable. /// /// The image - public static Image Load(string stream, Configuration config) + public static Image Load(string path, Configuration config) { - return Load(stream, null, config); + return Load(path, null, config); } /// - /// Loads the image from the given stream. + /// Loads the image from the given file. /// - /// The stream containing image information. + /// The file path to the image. /// The options for the decoder. /// The configuration options. /// /// Thrown if the stream is not readable nor seekable. /// /// The image - public static Image Load(string stream, IDecoderOptions options, Configuration config) + public static Image Load(string path, IDecoderOptions options, Configuration config) { - return new Image(Image.Load(stream, options, config)); + return new Image(Load(path, options, config)); } /// - /// Loads the image from the given stream. + /// Loads the image from the given file. /// /// The pixel format. - /// The stream containing image information. + /// The file path to the image. /// /// Thrown if the stream is not readable nor seekable. /// /// The image - public static Image Load(string stream) + public static Image Load(string path) where TColor : struct, IPixel { - return Load(stream, null, null); + return Load(path, null, null); } /// - /// Loads the image from the given stream. + /// Loads the image from the given file. /// /// The pixel format. - /// The stream containing image information. + /// 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 stream, IDecoderOptions options) + public static Image Load(string path, IDecoderOptions options) where TColor : struct, IPixel { - return Load(stream, options, null); + return Load(path, options, null); } /// - /// Loads the image from the given stream. + /// Loads the image from the given file. /// /// The pixel format. - /// The stream containing image information. + /// The file path to the image. /// The config for the decoder. /// /// Thrown if the stream is not readable nor seekable. /// /// The image - public static Image Load(string stream, Configuration config) + public static Image Load(string path, Configuration config) where TColor : struct, IPixel { - return Load(stream, null, config); + return Load(path, null, config); } /// - /// Loads the image from the given stream. + /// Loads the image from the given file. /// /// The pixel format. - /// The stream containing image information. + /// The file path to the image. /// The options for the decoder. /// The configuration options. /// /// Thrown if the stream is not readable nor seekable. /// /// The image - public static Image Load(string stream, IDecoderOptions options, Configuration config) + public static Image Load(string path, IDecoderOptions options, Configuration config) where TColor : struct, IPixel { config = config ?? Configuration.Default; - using (Stream s = config.FileSystem.OpenRead(stream)) + using (Stream s = config.FileSystem.OpenRead(path)) { return Load(s, options, config); } From 1343c78411010268a092ec0d83b3f4574daf4269 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Wed, 22 Mar 2017 06:58:16 +0000 Subject: [PATCH 49/83] Add IImageDecoder overloads Adds overloads the the Image.Load(..) methods that take an IImageDecoder. --- src/ImageSharp/Image.Decode.cs | 40 +++--- src/ImageSharp/Image.FromBytes.cs | 72 +++++++++- src/ImageSharp/Image.FromFile.cs | 70 ++++++++- src/ImageSharp/Image.FromStream.cs | 108 +++++++++++--- .../ImageSharp.Tests/Image/ImageLoadTests.cs | 133 +++++++++++++++++- tests/ImageSharp.Tests/TestFileSystem.cs | 72 ++++++++++ 6 files changed, 449 insertions(+), 46 deletions(-) create mode 100644 tests/ImageSharp.Tests/TestFileSystem.cs diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index f31f2c0a55..c95b71b1e8 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -19,25 +19,12 @@ namespace ImageSharp /// public sealed partial class Image { - /// - /// Decodes the image stream to the current image. - /// - /// The pixel format. - /// The stream. - /// The options for the decoder. - /// the configuration. - /// The decoded image - /// - /// [true] if can successfull decode the image otherwise [false]. - /// - private static bool Decode(Stream stream, IDecoderOptions options, Configuration config, out Image img) - where TColor : struct, IPixel + private static IImageFormat DiscoverFormat(Stream stream, IDecoderOptions options, Configuration config) { - img = null; int maxHeaderSize = config.MaxHeaderSize; if (maxHeaderSize <= 0) { - return false; + return null; } IImageFormat format; @@ -54,14 +41,31 @@ namespace ImageSharp 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, options, config); if (format == null) { - return false; + return null; } - img = format.Decoder.Decode(stream, options); + Image img = format.Decoder.Decode(stream, options); img.CurrentImageFormat = format; - return true; + return img; } } } diff --git a/src/ImageSharp/Image.FromBytes.cs b/src/ImageSharp/Image.FromBytes.cs index 3f1a3031c0..8e0ac04a5a 100644 --- a/src/ImageSharp/Image.FromBytes.cs +++ b/src/ImageSharp/Image.FromBytes.cs @@ -29,7 +29,7 @@ namespace ImageSharp /// The image public static Image Load(byte[] data) { - return Load(data, null, null); + return Load(data, null, (Configuration)null); } /// @@ -60,6 +60,20 @@ namespace ImageSharp return Load(data, null, config); } + /// + /// 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. /// @@ -78,6 +92,24 @@ namespace ImageSharp } } + /// + /// 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. /// @@ -90,7 +122,7 @@ namespace ImageSharp public static Image Load(byte[] data) where TColor : struct, IPixel { - return Load(data, null, null); + return Load(data, null, (Configuration)null); } /// @@ -125,6 +157,22 @@ namespace ImageSharp return Load(data, null, config); } + /// + /// 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. /// @@ -144,5 +192,25 @@ namespace ImageSharp return Load(ms, options, config); } } + + /// + /// 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 index 9b8ba83ded..e9015eaa9d 100644 --- a/src/ImageSharp/Image.FromFile.cs +++ b/src/ImageSharp/Image.FromFile.cs @@ -30,7 +30,7 @@ namespace ImageSharp /// The image public static Image Load(string path) { - return Load(path, null, null); + return Load(path, null, (Configuration)null); } /// @@ -61,6 +61,35 @@ namespace ImageSharp return Load(path, null, config); } + /// + /// 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. /// @@ -88,7 +117,7 @@ namespace ImageSharp public static Image Load(string path) where TColor : struct, IPixel { - return Load(path, null, null); + return Load(path, null, (Configuration)null); } /// @@ -123,6 +152,22 @@ namespace ImageSharp return Load(path, null, config); } + /// + /// 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. /// @@ -143,6 +188,27 @@ namespace ImageSharp return Load(s, options, config); } } + + /// + /// 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 index 36309db5b5..b40aa62cd9 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -29,7 +29,7 @@ namespace ImageSharp /// The image public static Image Load(Stream stream) { - return Load(stream, null, null); + return Load(stream, null, (Configuration)null); } /// @@ -60,6 +60,20 @@ namespace ImageSharp return Load(stream, null, config); } + /// + /// 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. /// @@ -75,6 +89,21 @@ namespace ImageSharp return new Image(Load(stream, options, config)); } + /// + /// 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) + { + return new Image(Load(stream, decoder, options)); + } + /// /// Loads the image from the given stream. /// @@ -87,7 +116,7 @@ namespace ImageSharp public static Image Load(Stream stream) where TColor : struct, IPixel { - return Load(stream, null, null); + return Load(stream, null, (Configuration)null); } /// @@ -122,6 +151,39 @@ namespace ImageSharp return Load(stream, null, config); } + /// + /// 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(s, options)); + } + /// /// Loads the image from the given stream. /// @@ -138,11 +200,29 @@ namespace ImageSharp { config = config ?? Configuration.Default; - if (!config.ImageFormats.Any()) + Image img = WithSeekableStream(stream, s => + { + return 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) { - throw new InvalidOperationException("No image formats have been configured."); + 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."); @@ -150,10 +230,7 @@ namespace ImageSharp if (stream.CanSeek) { - if (Decode(stream, options, config, out Image img)) - { - return img; - } + return action(stream); } else { @@ -163,22 +240,9 @@ namespace ImageSharp stream.CopyTo(ms); ms.Position = 0; - if (Decode(ms, options, config, out Image img)) - { - return img; - } + return action(stream); } } - - 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()); } } } diff --git a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs index 2c97ea06fd..da3d2fb069 100644 --- a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs @@ -66,6 +66,9 @@ namespace ImageSharp.Tests 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] @@ -157,6 +160,50 @@ namespace ImageSharp.Tests this.localDecoder.Verify(x => x.Decode(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(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(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(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(stream, this.decoderOptions)); + } + [Fact] public void LoadFromBytes() { @@ -246,7 +293,50 @@ namespace ImageSharp.Tests this.localDecoder.Verify(x => x.Decode(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(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(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(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(It.IsAny(), this.decoderOptions)); + Assert.Equal(this.DataStream.ToArray(), this.DecodedData); + } + [Fact] public void LoadFromFile() { @@ -324,7 +414,7 @@ namespace ImageSharp.Tests [Fact] public void LoadFromFileWithTypeAndConfigAndOptions() { - Image img = Image.Load(FilePath, this.decoderOptions, this.LocalConfiguration); + Image img = Image.Load(this.FilePath, this.decoderOptions, this.LocalConfiguration); Assert.NotNull(img); Assert.Equal(this.returnImage, img); @@ -332,6 +422,45 @@ namespace ImageSharp.Tests this.localDecoder.Verify(x => x.Decode(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(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(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(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(this.DataStream, this.decoderOptions)); + } + public void Dispose() { // clean up the global object; diff --git a/tests/ImageSharp.Tests/TestFileSystem.cs b/tests/ImageSharp.Tests/TestFileSystem.cs new file mode 100644 index 0000000000..78499ac8ee --- /dev/null +++ b/tests/ImageSharp.Tests/TestFileSystem.cs @@ -0,0 +1,72 @@ +// +// 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) + { + MemoryStream stream = new MemoryStream(); + lock (fileSystem) + { + if (fileSystem.ContainsKey(path)) + { + fileSystem[path] = stream; + } + else + { + fileSystem.Add(path, stream); + } + } + return stream; + } + + + public Stream OpenRead(string path) + { + lock (fileSystem) + { + if (fileSystem.ContainsKey(path)) + { + + Stream stream = fileSystem[path]; + stream.Position = 0; + return stream; + } + } + + return File.OpenRead(path); + } + } +} + From cfce9d2efc8bf3210dfc2b1f823b2c2c136ff51a Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Wed, 22 Mar 2017 07:09:43 +0000 Subject: [PATCH 50/83] remove decode metadata only it has no tests and nothing called it --- src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index fa656e71e8..3f946fb790 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -193,19 +193,6 @@ namespace ImageSharp.Formats return image; } - /// - /// Decodes the image from the specified and sets - /// the data to image. - /// - /// The stream, where the image should be. - /// The image metadata. - public ImageMetaData DecodeMetaData(Stream stream) - { - ImageMetaData metadata = new ImageMetaData(); - this.ProcessStream(metadata, stream, true); - return metadata; - } - /// /// Dispose /// From 53bba0269aa741148c33342816c276d1c696afcb Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Wed, 22 Mar 2017 09:56:22 +0000 Subject: [PATCH 51/83] pass configuration & ensure only single image created --- src/ImageSharp/Formats/Bmp/BmpDecoder.cs | 5 +- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 14 +- src/ImageSharp/Formats/Gif/GifDecoder.cs | 10 +- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 11 +- src/ImageSharp/Formats/IImageDecoder.cs | 3 +- src/ImageSharp/Formats/Jpeg/JpegDecoder.cs | 4 +- .../Formats/Jpeg/JpegDecoderCore.cs | 12 +- src/ImageSharp/Formats/Png/PngDecoder.cs | 10 +- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 11 +- src/ImageSharp/Image.Create.cs | 46 +++++++ src/ImageSharp/Image.Decode.cs | 6 +- src/ImageSharp/Image.FromFile.cs | 6 +- src/ImageSharp/Image.FromStream.cs | 2 +- .../Formats/Jpg/JpegDecoderTests.cs | 2 +- .../ImageSharp.Tests/Image/ImageLoadTests.cs | 128 ++++++++++++------ tests/ImageSharp.Tests/TestFormat.cs | 23 +++- 16 files changed, 221 insertions(+), 72 deletions(-) create mode 100644 src/ImageSharp/Image.Create.cs diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index 2bc1c8cc30..8414807e0c 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -26,12 +26,13 @@ namespace ImageSharp.Formats public class BmpDecoder : IImageDecoder { /// - public Image Decode(Stream stream, IDecoderOptions options) + public Image Decode(Stream stream, IDecoderOptions options, Configuration configuration) + where TColor : struct, IPixel { Guard.NotNull(stream, "stream"); - return new BmpDecoderCore().Decode(stream); + return new BmpDecoderCore(configuration).Decode(stream); } } } diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index adfa4b6ace..18e4e858b8 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -43,6 +43,17 @@ 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. @@ -114,8 +125,7 @@ namespace ImageSharp.Formats + $"bigger then the max allowed size '{Image.MaxWidth}x{Image.MaxHeight}'"); } - Image image = new Image(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) diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index 16b036e684..2e56b0baba 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -14,12 +14,13 @@ namespace ImageSharp.Formats public class GifDecoder : IImageDecoder { /// - public Image Decode(Stream stream, IDecoderOptions options) + public Image Decode(Stream stream, IDecoderOptions options, Configuration configuration) + where TColor : struct, IPixel { IGifDecoderOptions gifOptions = GifDecoderOptions.Create(options); - return this.Decode(stream, gifOptions); + return this.Decode(stream, gifOptions, configuration); } /// @@ -28,11 +29,12 @@ namespace ImageSharp.Formats /// The pixel format. /// The containing image data. /// The options for the decoder. + /// The configuration. /// The image thats been decoded. - public Image Decode(Stream stream, IGifDecoderOptions options) + public Image Decode(Stream stream, IGifDecoderOptions options, Configuration configuration) where TColor : struct, IPixel { - return new GifDecoderCore(options).Decode(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 22a26345fe..79348a7aba 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -27,6 +27,11 @@ namespace ImageSharp.Formats /// private readonly IGifDecoderOptions options; + /// + /// The global configuration. + /// + private readonly Configuration configuration; + /// /// The currently loaded stream. /// @@ -76,9 +81,11 @@ namespace ImageSharp.Formats /// 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; } /// @@ -355,7 +362,7 @@ namespace ImageSharp.Formats this.metaData.Quality = colorTableLength / 3; // This initializes the image to become fully transparent because the alpha channel is zero. - this.image = new Image(imageWidth, imageHeight); + this.image = Image.Create(imageWidth, imageHeight, this.configuration); this.image.MetaData.LoadFrom(this.metaData); this.SetFrameDelay(this.metaData); diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs index c4a9cf8c3c..8da23d499f 100644 --- a/src/ImageSharp/Formats/IImageDecoder.cs +++ b/src/ImageSharp/Formats/IImageDecoder.cs @@ -19,8 +19,9 @@ namespace ImageSharp.Formats /// The pixel format. /// The containing image data. /// The options for the decoder. + /// The configuration for the image. /// The decoded image - Image Decode(Stream stream, IDecoderOptions options) + Image Decode(Stream stream, IDecoderOptions options, Configuration configuration) where TColor : struct, IPixel; } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index 3a91f8010e..254506af05 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -14,12 +14,12 @@ namespace ImageSharp.Formats public class JpegDecoder : IImageDecoder { /// - public Image Decode(Stream stream, IDecoderOptions options) + public Image Decode(Stream stream, IDecoderOptions options, Configuration configuration) where TColor : struct, IPixel { Guard.NotNull(stream, "stream"); - using (JpegDecoderCore decoder = new JpegDecoderCore(options)) + using (JpegDecoderCore decoder = new JpegDecoderCore(options, configuration)) { return decoder.Decode(stream); } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 3f946fb790..fc9506b546 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]; @@ -498,7 +505,8 @@ namespace ImageSharp.Formats private Image ConvertJpegPixelsToImagePixels(ImageMetaData metadata) where TColor : struct, IPixel { - Image image = new Image(this.ImageWidth, this.ImageHeight); + Image image = Image.Create(this.ImageWidth, this.ImageHeight, this.configuration); + image.MetaData.LoadFrom(metadata); if (this.grayImage.IsInitialized) diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index 5b7d97fc70..bf17ad1428 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -31,12 +31,13 @@ namespace ImageSharp.Formats public class PngDecoder : IImageDecoder { /// - public Image Decode(Stream stream, IDecoderOptions options) + public Image Decode(Stream stream, IDecoderOptions options, Configuration configuration) + where TColor : struct, IPixel { IPngDecoderOptions pngOptions = PngDecoderOptions.Create(options); - return this.Decode(stream, pngOptions); + return this.Decode(stream, pngOptions, configuration); } /// @@ -45,11 +46,12 @@ namespace ImageSharp.Formats /// The pixel format. /// The containing image data. /// The options for the decoder. + /// The configuration for the image. /// The decoded image. - public Image Decode(Stream stream, IPngDecoderOptions options) + public Image Decode(Stream stream, IPngDecoderOptions options, Configuration configuration) where TColor : struct, IPixel { - return new PngDecoderCore(options).Decode(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 dadf7ab7d6..7705e95492 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(); } @@ -213,7 +220,7 @@ namespace ImageSharp.Formats 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 image = new Image(this.header.Width, this.header.Height); + Image image = Image.Create(this.header.Width, this.header.Height, this.configuration); image.MetaData.LoadFrom(metadata); using (PixelAccessor pixels = image.Lock()) diff --git a/src/ImageSharp/Image.Create.cs b/src/ImageSharp/Image.Create.cs new file mode 100644 index 0000000000..bc430724a6 --- /dev/null +++ b/src/ImageSharp/Image.Create.cs @@ -0,0 +1,46 @@ +// +// 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 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 + { + if (typeof(TColor) == typeof(Color)) + { + return new Image(width, height, configuration) as Image; + } + else + { + return new Image(width, height, configuration); + } + } + } +} diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index c95b71b1e8..32943ead58 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -19,7 +19,7 @@ namespace ImageSharp /// public sealed partial class Image { - private static IImageFormat DiscoverFormat(Stream stream, IDecoderOptions options, Configuration config) + private static IImageFormat DiscoverFormat(Stream stream, Configuration config) { int maxHeaderSize = config.MaxHeaderSize; if (maxHeaderSize <= 0) @@ -57,13 +57,13 @@ namespace ImageSharp private static Image Decode(Stream stream, IDecoderOptions options, Configuration config) where TColor : struct, IPixel { - IImageFormat format = DiscoverFormat(stream, options, config); + IImageFormat format = DiscoverFormat(stream, config); if (format == null) { return null; } - Image img = format.Decoder.Decode(stream, options); + Image img = format.Decoder.Decode(stream, options, config); img.CurrentImageFormat = format; return img; } diff --git a/src/ImageSharp/Image.FromFile.cs b/src/ImageSharp/Image.FromFile.cs index e9015eaa9d..b7fb0eef59 100644 --- a/src/ImageSharp/Image.FromFile.cs +++ b/src/ImageSharp/Image.FromFile.cs @@ -102,7 +102,11 @@ namespace ImageSharp /// The image public static Image Load(string path, IDecoderOptions options, Configuration config) { - return new Image(Load(path, options, config)); + config = config ?? Configuration.Default; + using (Stream s = config.FileSystem.OpenRead(path)) + { + return Load(s, options, config); + } } /// diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index b40aa62cd9..e995c47997 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -181,7 +181,7 @@ namespace ImageSharp public static Image Load(Stream stream, IImageDecoder decoder, IDecoderOptions options) where TColor : struct, IPixel { - return WithSeekableStream(stream, s => decoder.Decode(s, options)); + return WithSeekableStream(stream, s => decoder.Decode(s, options, Configuration.Default)); } /// diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 416c88a500..cdd892dcee 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -90,7 +90,7 @@ namespace ImageSharp.Tests image.Save(ms, new JpegEncoder()); ms.Seek(0, SeekOrigin.Begin); - using (JpegDecoderCore decoder = new JpegDecoderCore(null)) + using (JpegDecoderCore decoder = new JpegDecoderCore(null, null)) { Image mirror = decoder.Decode(ms); diff --git a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs index da3d2fb069..52b8f6ee28 100644 --- a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs @@ -32,7 +32,7 @@ namespace ImageSharp.Tests public ImageLoadTests() { - this.returnImage = new Image(1, 1); + this.returnImage = new Image(1, 1); this.localDecoder = new Mock(); this.localFormat = new Mock(); @@ -43,8 +43,10 @@ namespace ImageSharp.Tests 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())) - .Callback((s, o) => { + + this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny(), It.IsAny())) + + .Callback((s, o, c) => { using (var ms = new MemoryStream()) { s.CopyTo(ms); @@ -79,7 +81,9 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); - TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null); + + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, Configuration.Default); + } [Fact] @@ -90,7 +94,9 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(TestFormat.GlobalTestFormat.Sample(), img); Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); - TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null); + + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, Configuration.Default); + } [Fact] @@ -100,7 +106,9 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); - TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, this.decoderOptions); + + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, this.decoderOptions, Configuration.Default); + } [Fact] @@ -111,7 +119,9 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(TestFormat.GlobalTestFormat.Sample(), img); Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); - TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, this.decoderOptions); + + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, this.decoderOptions, Configuration.Default); + } [Fact] @@ -122,7 +132,9 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - this.localDecoder.Verify(x => x.Decode(stream, null)); + + this.localDecoder.Verify(x => x.Decode(stream, null, this.LocalConfiguration)); + } [Fact] @@ -134,7 +146,9 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.returnImage, img); Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - this.localDecoder.Verify(x => x.Decode(stream, null)); + + this.localDecoder.Verify(x => x.Decode(stream, null, this.LocalConfiguration)); + } [Fact] @@ -145,7 +159,9 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - this.localDecoder.Verify(x => x.Decode(stream, this.decoderOptions)); + + this.localDecoder.Verify(x => x.Decode(stream, this.decoderOptions, this.LocalConfiguration)); + } [Fact] @@ -157,7 +173,9 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.returnImage, img); Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - this.localDecoder.Verify(x => x.Decode(stream, this.decoderOptions)); + + this.localDecoder.Verify(x => x.Decode(stream, this.decoderOptions, this.LocalConfiguration)); + } @@ -169,7 +187,7 @@ namespace ImageSharp.Tests Image img = Image.Load(stream, this.localDecoder.Object); Assert.NotNull(img); - this.localDecoder.Verify(x => x.Decode(stream, null)); + this.localDecoder.Verify(x => x.Decode(stream, null, Configuration.Default)); } [Fact] @@ -180,7 +198,7 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.returnImage, img); - this.localDecoder.Verify(x => x.Decode(stream, null)); + this.localDecoder.Verify(x => x.Decode(stream, null, Configuration.Default)); } [Fact] @@ -190,7 +208,7 @@ namespace ImageSharp.Tests Image img = Image.Load(stream, this.localDecoder.Object, this.decoderOptions); Assert.NotNull(img); - this.localDecoder.Verify(x => x.Decode(stream, this.decoderOptions)); + this.localDecoder.Verify(x => x.Decode(stream, this.decoderOptions, Configuration.Default)); } [Fact] @@ -201,7 +219,7 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.returnImage, img); - this.localDecoder.Verify(x => x.Decode(stream, this.decoderOptions)); + this.localDecoder.Verify(x => x.Decode(stream, this.decoderOptions, Configuration.Default)); } [Fact] @@ -212,7 +230,9 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); - TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null); + + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, Configuration.Default); + } [Fact] @@ -223,7 +243,9 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(TestFormat.GlobalTestFormat.Sample(), img); Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); - TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null); + + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, Configuration.Default); + } [Fact] @@ -233,7 +255,9 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); - TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, this.decoderOptions); + + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, this.decoderOptions, Configuration.Default); + } [Fact] @@ -244,7 +268,9 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(TestFormat.GlobalTestFormat.Sample(), img); Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); - TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, this.decoderOptions); + + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, this.decoderOptions, Configuration.Default); + } [Fact] @@ -254,7 +280,9 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - this.localDecoder.Verify(x => x.Decode(It.IsAny(), null)); + + this.localDecoder.Verify(x => x.Decode(It.IsAny(), null, this.LocalConfiguration)); + Assert.Equal(this.DataStream.ToArray(), this.DecodedData); } @@ -267,7 +295,9 @@ namespace ImageSharp.Tests Assert.Equal(this.returnImage, img); Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - this.localDecoder.Verify(x => x.Decode(It.IsAny(), null)); + + this.localDecoder.Verify(x => x.Decode(It.IsAny(), null, this.LocalConfiguration)); + Assert.Equal(this.DataStream.ToArray(), this.DecodedData); } @@ -278,7 +308,9 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - this.localDecoder.Verify(x => x.Decode(It.IsAny(), this.decoderOptions)); + + this.localDecoder.Verify(x => x.Decode(It.IsAny(), this.decoderOptions, this.LocalConfiguration)); + Assert.Equal(this.DataStream.ToArray(), this.DecodedData); } @@ -290,7 +322,9 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.returnImage, img); Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - this.localDecoder.Verify(x => x.Decode(It.IsAny(), this.decoderOptions)); + + this.localDecoder.Verify(x => x.Decode(It.IsAny(), this.decoderOptions, this.LocalConfiguration)); + Assert.Equal(this.DataStream.ToArray(), this.DecodedData); } @@ -301,7 +335,7 @@ namespace ImageSharp.Tests Image img = Image.Load(this.DataStream.ToArray(), this.localDecoder.Object); Assert.NotNull(img); - this.localDecoder.Verify(x => x.Decode(It.IsAny(), null)); + this.localDecoder.Verify(x => x.Decode(It.IsAny(), null, Configuration.Default)); Assert.Equal(this.DataStream.ToArray(), this.DecodedData); } @@ -312,7 +346,7 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.returnImage, img); - this.localDecoder.Verify(x => x.Decode(It.IsAny(), null)); + this.localDecoder.Verify(x => x.Decode(It.IsAny(), null, Configuration.Default)); Assert.Equal(this.DataStream.ToArray(), this.DecodedData); } @@ -322,7 +356,7 @@ namespace ImageSharp.Tests Image img = Image.Load(this.DataStream.ToArray(), this.localDecoder.Object, this.decoderOptions); Assert.NotNull(img); - this.localDecoder.Verify(x => x.Decode(It.IsAny(), this.decoderOptions)); + this.localDecoder.Verify(x => x.Decode(It.IsAny(), this.decoderOptions, Configuration.Default)); Assert.Equal(this.DataStream.ToArray(), this.DecodedData); } @@ -333,7 +367,7 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.returnImage, img); - this.localDecoder.Verify(x => x.Decode(It.IsAny(), this.decoderOptions)); + this.localDecoder.Verify(x => x.Decode(It.IsAny(), this.decoderOptions, Configuration.Default)); Assert.Equal(this.DataStream.ToArray(), this.DecodedData); } @@ -345,7 +379,9 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); - TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null); + + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, Configuration.Default); + } [Fact] @@ -356,7 +392,9 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(TestFormat.GlobalTestFormat.Sample(), img); Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); - TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null); + + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, Configuration.Default); + } [Fact] @@ -366,7 +404,9 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); - TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, this.decoderOptions); + + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, this.decoderOptions, Configuration.Default); + } [Fact] @@ -377,7 +417,9 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(TestFormat.GlobalTestFormat.Sample(), img); Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); - TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, this.decoderOptions); + + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, this.decoderOptions, Configuration.Default); + } [Fact] @@ -387,7 +429,9 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - this.localDecoder.Verify(x => x.Decode(this.DataStream, null)); + + this.localDecoder.Verify(x => x.Decode(this.DataStream, null, this.LocalConfiguration)); + } [Fact] @@ -398,7 +442,9 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.returnImage, img); Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - this.localDecoder.Verify(x => x.Decode(this.DataStream, null)); + + this.localDecoder.Verify(x => x.Decode(this.DataStream, null, this.LocalConfiguration)); + } [Fact] @@ -408,7 +454,9 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - this.localDecoder.Verify(x => x.Decode(this.DataStream, this.decoderOptions)); + + this.localDecoder.Verify(x => x.Decode(this.DataStream, this.decoderOptions, this.LocalConfiguration)); + } [Fact] @@ -419,7 +467,9 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.returnImage, img); Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - this.localDecoder.Verify(x => x.Decode(this.DataStream, this.decoderOptions)); + + this.localDecoder.Verify(x => x.Decode(this.DataStream, this.decoderOptions, this.LocalConfiguration)); + } @@ -429,7 +479,7 @@ namespace ImageSharp.Tests Image img = Image.Load(this.FilePath, this.localDecoder.Object); Assert.NotNull(img); - this.localDecoder.Verify(x => x.Decode(this.DataStream, null)); + this.localDecoder.Verify(x => x.Decode(this.DataStream, null, Configuration.Default)); } [Fact] @@ -439,7 +489,7 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.returnImage, img); - this.localDecoder.Verify(x => x.Decode(this.DataStream, null)); + this.localDecoder.Verify(x => x.Decode(this.DataStream, null, Configuration.Default)); } [Fact] @@ -448,7 +498,7 @@ namespace ImageSharp.Tests Image img = Image.Load(this.FilePath, this.localDecoder.Object, this.decoderOptions); Assert.NotNull(img); - this.localDecoder.Verify(x => x.Decode(this.DataStream, this.decoderOptions)); + this.localDecoder.Verify(x => x.Decode(this.DataStream, this.decoderOptions, Configuration.Default)); } [Fact] @@ -458,7 +508,7 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.returnImage, img); - this.localDecoder.Verify(x => x.Decode(this.DataStream, this.decoderOptions)); + this.localDecoder.Verify(x => x.Decode(this.DataStream, this.decoderOptions, Configuration.Default)); } public void Dispose() diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index 3a40ed4201..5894151dca 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -55,9 +55,11 @@ namespace ImageSharp.Tests Dictionary _sampleImages = new Dictionary(); - public void VerifyDecodeCall(byte[] marker, IDecoderOptions options) + + public void VerifyDecodeCall(byte[] marker, IDecoderOptions options, Configuration config) { - DecodeOperation[] discovered = this.DecodeCalls.Where(x => x.IsMatch(marker, options)).ToArray(); + 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"); @@ -107,10 +109,16 @@ namespace ImageSharp.Tests { public byte[] marker; public IDecoderOptions options; + internal Configuration config; - public bool IsMatch(byte[] testMarker, IDecoderOptions testOptions) + public bool IsMatch(byte[] testMarker, IDecoderOptions testOptions, Configuration config) { - if(this.options != testOptions) + if (this.options != testOptions) + { + return false; + } + + if (this.config != config) { return false; } @@ -140,7 +148,9 @@ namespace ImageSharp.Tests this.testFormat = testFormat; } - public Image Decode(Stream stream, IDecoderOptions options) where TColor : struct, IPixel + + public Image Decode(Stream stream, IDecoderOptions options, Configuration config) where TColor : struct, IPixel + { var ms = new MemoryStream(); stream.CopyTo(ms); @@ -148,7 +158,8 @@ namespace ImageSharp.Tests this.testFormat.DecodeCalls.Add(new DecodeOperation { marker = marker, - options = options + options = options, + config = config }); // TODO record this happend so we an verify it. From fb5899f0fdfed6a3ad8bb15dfa5ed165e2a41933 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Wed, 22 Mar 2017 10:14:13 +0000 Subject: [PATCH 52/83] fix test filesystem to prevent it breaking existing tests --- tests/ImageSharp.Tests/TestFileSystem.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/tests/ImageSharp.Tests/TestFileSystem.cs b/tests/ImageSharp.Tests/TestFileSystem.cs index 78499ac8ee..d43b989f10 100644 --- a/tests/ImageSharp.Tests/TestFileSystem.cs +++ b/tests/ImageSharp.Tests/TestFileSystem.cs @@ -36,29 +36,28 @@ namespace ImageSharp.Tests public Stream Create(string path) { - MemoryStream stream = new MemoryStream(); + // if we have injected a fake file use it instead lock (fileSystem) { if (fileSystem.ContainsKey(path)) { - fileSystem[path] = stream; - } - else - { - fileSystem.Add(path, stream); + Stream stream = fileSystem[path]; + stream.Position = 0; + return stream; } } - 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; From 152e71135c117e2396c265a6113e2cbf0c74ac39 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Thu, 23 Mar 2017 10:08:30 +0000 Subject: [PATCH 53/83] add range checks --- .../Processors/FillRegionProcessor.cs | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs index 66e1f4380c..3c07d41a86 100644 --- a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs @@ -65,6 +65,15 @@ namespace ImageSharp.Drawing.Processors 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; @@ -122,22 +131,32 @@ namespace ImageSharp.Drawing.Processors int startX = (int)Math.Floor(scanStart); int endX = (int)Math.Floor(scanEnd); - for (float x = scanStart; x < startX + 1; x += subpixelFraction) + if (startX >= 0 && startX < scanline.Length) { - scanline[startX] += subpixelFractionPoint; - scanlineDirty = true; + for (float x = scanStart; x < startX + 1; x += subpixelFraction) + { + scanline[startX] += subpixelFractionPoint; + scanlineDirty = true; + } } - for (float x = endX; x < scanEnd; x += subpixelFraction) + if (endX >= 0 && endX < scanline.Length) { - scanline[endX] += subpixelFractionPoint; - scanlineDirty = true; + for (float x = endX; x < scanEnd; x += subpixelFraction) + { + scanline[endX] += subpixelFractionPoint; + scanlineDirty = true; + } } - for (int x = startX + 1; x < endX; x++) + int nextX = startX + 1; + if (nextX >= 0 && nextX < scanline.Length && endX >= 0 && endX < scanline.Length) { - scanline[x] += subpixelFraction; - scanlineDirty = true; + for (int x = nextX; x < endX; x++) + { + scanline[x] += subpixelFraction; + scanlineDirty = true; + } } } } From 3db50fea44216e62dd4fb7ac46d48ad5e1f3211b Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Thu, 23 Mar 2017 10:15:59 +0000 Subject: [PATCH 54/83] reduce range check --- src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs index 3c07d41a86..060ead6e4d 100644 --- a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs @@ -150,7 +150,7 @@ namespace ImageSharp.Drawing.Processors } int nextX = startX + 1; - if (nextX >= 0 && nextX < scanline.Length && endX >= 0 && endX < scanline.Length) + if (nextX >= 0 && endX < scanline.Length) { for (int x = nextX; x < endX; x++) { From 69daa8464a30a62d5901d986a3ab620bbbbcff34 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Thu, 23 Mar 2017 10:18:25 +0000 Subject: [PATCH 55/83] scan to end of line even if longer --- src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs index 060ead6e4d..9a616d408b 100644 --- a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs @@ -150,7 +150,8 @@ namespace ImageSharp.Drawing.Processors } int nextX = startX + 1; - if (nextX >= 0 && endX < scanline.Length) + endX = Math.Min(endX, scanline.Length); // reduce to end to the right edge + if (nextX >= 0) { for (int x = nextX; x < endX; x++) { From 422ff51502745fdd7311f94872f817fd199d28dd Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Thu, 23 Mar 2017 15:21:13 +0000 Subject: [PATCH 56/83] Default text drawing at an image dpi independent size This changes the default behavour to by defaulting all text to be drawn as 72 dpi as apposed to the images native resolution thus ensuing that the pixel hight per point is consisten across images by defualt. Can be switched back to image resolution byt setting `UseImageResolution` in `TextGraphicsOptions` to `true` --- src/ImageSharp.Drawing/Text/DrawText.cs | 9 ++++++- .../Text/TextGraphicsOptions.cs | 7 +++++ .../ImageSharp.Tests/Drawing/Text/DrawText.cs | 27 +++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) 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 253bb2aaca..b58a40b34d 100644 --- a/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs +++ b/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs @@ -35,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. /// @@ -45,6 +51,7 @@ namespace ImageSharp.Drawing this.ApplyKerning = true; this.TabWidth = 4; this.AntialiasSubpixelDepth = 16; + this.UseImageResolution = false; } /// diff --git a/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs b/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs index 68db4d9a27..52b7fcbb65 100644 --- a/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs +++ b/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs @@ -219,5 +219,32 @@ namespace ImageSharp.Tests.Drawing.Text 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); + } } } From e6e97ed4041cf2b6ef1aa6920be7cc4af97868d7 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 24 Mar 2017 07:49:10 +0000 Subject: [PATCH 57/83] Add image comparer test helper Add a couple of png smoke tests that encode and then decode an image in png then does a visual compare to verify they render the same. --- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 5 +- .../Formats/Png/PngInterlaceMode.cs | 4 +- .../Formats/Png/PngEncoderTests.cs | 64 +++++- .../Formats/Png/PngSmokeTests.cs | 62 ++++++ tests/ImageSharp.Tests/ImageComparer.cs | 84 ++++++++ .../WithTestPatternImageAttribute.cs | 38 ++++ .../ImageProviders/TestImageProvider.cs | 13 +- .../ImageProviders/TestPatternProvider.cs | 184 ++++++++++++++++++ .../TestUtilities/ImagingTestCaseUtility.cs | 34 +++- 9 files changed, 470 insertions(+), 18 deletions(-) create mode 100644 tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs create mode 100644 tests/ImageSharp.Tests/ImageComparer.cs create mode 100644 tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImageAttribute.cs create mode 100644 tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 7950d260c7..e97eaed582 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -545,11 +545,10 @@ namespace ImageSharp.Formats /// The pixel format. /// The containing image data. /// The image base. - private void WritePhysicalChunk(Stream stream, ImageBase imageBase) + 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/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 49be751391..16906c1fa8 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -9,12 +9,12 @@ namespace ImageSharp.Tests { using System.IO; using System.Threading.Tasks; - + using ImageSharp.IO; using Xunit; public class PngEncoderTests : FileTestBase { - [Fact] + [Fact(Skip ="Slow intergration test")] public void ImageCanSaveIndexedPng() { string path = CreateOutputDirectory("Png", "Indexed"); @@ -32,7 +32,7 @@ namespace ImageSharp.Tests } } - [Fact] + [Fact(Skip = "Slow intergration test")] public void ImageCanSavePngInParallel() { string path = this.CreateOutputDirectory("Png"); @@ -50,5 +50,63 @@ namespace ImageSharp.Tests } }); } + + [Theory] + [WithBlankImages(1, 1, PixelTypes.All)] + public void WritesFileMarker(TestImageProvider provider) + where TColor : struct, IPixel + { + using (Image image = provider.GetImage()) + using (EndianBinaryReader reader = Encode(image, null)) + { + + byte[] data = reader.ReadBytes(8); + 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 + }; + + Assert.Equal(expected, data); + } + } + + [Theory] + [WithBlankImages(1, 1, PixelTypes.All)] + [WithBlankImages(10, 10, PixelTypes.StandardImageClass)] + public void WritesFileHeaderHasHeightAndWidth(TestImageProvider provider) + where TColor : struct, IPixel + { + using (Image image = provider.GetImage()) + using (EndianBinaryReader reader = Encode(image, null)) + { + reader.ReadBytes(8); // throw away the file header + uint width = reader.ReadUInt32(); + uint height = reader.ReadUInt32(); + + byte bitDepth = reader.ReadByte(); + byte colorType = reader.ReadByte(); + byte compressionMethod = reader.ReadByte(); + byte filterMethod = reader.ReadByte(); + byte interlaceMethod = reader.ReadByte(); + + Assert.Equal(image.Width, (int)width); + Assert.Equal(image.Height, (int)height); + } + } + + private static EndianBinaryReader Encode(Image img, IEncoderOptions options) + where TColor : struct, IPixel + { + MemoryStream stream = new MemoryStream(); + new PngEncoder().Encode(img, stream, null); + stream.Position = 0; + return new EndianBinaryReader(Endianness.BigEndian, stream); + } } } \ 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..7a15e30e99 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs @@ -0,0 +1,62 @@ +// +// 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 WritesFileMarker(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 = new Image(ms, new Configuration(new PngFormat()))) + { + img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder()); + ImageComparer.VisualComparer(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 = new Image(ms, new Configuration(new PngFormat()))) + { + ImageComparer.VisualComparer(image, img2); + } + } + } + } +} diff --git a/tests/ImageSharp.Tests/ImageComparer.cs b/tests/ImageSharp.Tests/ImageComparer.cs new file mode 100644 index 0000000000..091edd3432 --- /dev/null +++ b/tests/ImageSharp.Tests/ImageComparer.cs @@ -0,0 +1,84 @@ +namespace ImageSharp.Tests +{ + using System; + using ImageSharp; + using Xunit; + + /// + /// Class to perform simple image comparisons. + /// + public static class ImageComparer + { + const int DefaultScalingFactor = 32; + const int DefaultSegmentThreshold = 3; + const float DefaultImageThreshold = 0.000f; + + public static void VisualComparer(Image expected, Image actual, float imageTheshold = DefaultImageThreshold, byte segmentThreshold = DefaultSegmentThreshold, int scalingFactor = DefaultScalingFactor) + where TColorA : struct, IPixel + where TColorB : struct, IPixel + { + var percentage = expected.PercentageDifference(actual, segmentThreshold, scalingFactor); + + Assert.InRange(percentage, 0, imageTheshold); + } + + 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/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/ImageProviders/TestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs index cdb31ab69a..4ec0fb5074 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs @@ -25,13 +25,20 @@ namespace ImageSharp.Tests public ImagingTestCaseUtility Utility { get; private set; } public GenericFactory Factory { get; private set; } = new GenericFactory(); - - 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, diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs new file mode 100644 index 0000000000..8d5e77554d --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs @@ -0,0 +1,184 @@ +// +// 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; + + public abstract partial class TestImageProvider + where TColor : struct, IPixel + { + private class TestPatternProvider : TestImageProvider + { + static Dictionary> testImages = new Dictionary>(); + + public TestPatternProvider(int width, int height) + { + this.Width = width; + this.Height = height; + } + + public override string SourceFileOrDescription => $"TestPattern{this.Width}x{this.Height}"; + + protected int Height { get; } + + protected int Width { get; } + + public override Image GetImage() + { + lock (testImages) + { + if (!testImages.ContainsKey(this.SourceFileOrDescription)) + { + var image = new Image(this.Width, this.Height); + DrawTestPattern(image); + testImages.Add(this.SourceFileOrDescription, image); + } + + return new Image(testImages[this.SourceFileOrDescription]); + } + } + + private static void DrawTestPattern(Image image) + { + // first lets split the image into 4 quadrants + using (var pixels = image.Lock()) + { + BlackWhiteChecker(pixels); // top left + HorizontalLines(pixels); // top right + TransparentGradients(pixels); // bottom left + Raninbow(pixels); // bottom right + } + } + + private static void HorizontalLines(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 (var y = top; y < bottom; y++) + { + for (var x = left; x < right; x++) + { + if (x % stride == 0) + { + p++; + p = p % c.Length; + } + pixels[x, y] = c[p]; + } + } + } + + 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 (var y = top; y < bottom; y++) + { + if (y % stride == 0) + { + p++; + p = p % c.Length; + } + var pstart = p; + for (var x = left; x < right; x++) + { + if (x % stride == 0) + { + p++; + p = p % c.Length; + } + pixels[x, y] = c[p]; + } + p = pstart; + } + } + + 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 (var x = left; x < right; x++) + { + blue.W = red.W = green.W = (float)x / (float)right; + + c.PackFromVector4(red); + var topBand = top; + for (var y = topBand; y < top + height; y++) + { + pixels[x, y] = c; + } + topBand = topBand + height; + c.PackFromVector4(green); + for (var y = topBand; y < topBand + height; y++) + { + pixels[x, y] = c; + } + topBand = topBand + height; + c.PackFromVector4(blue); + for (var y = topBand; y < bottom; y++) + { + pixels[x, y] = c; + } + } + + } + private static void Raninbow(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); + uint inital = 0; + for (var x = left; x < right; x++) + for (var y = top; y < bottom; y++) + { + t.PackedValue += stepsPerPixel; + var 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 bcccd1b44d..1c960e0e86 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,7 +100,7 @@ namespace ImageSharp.Tests where TColor : struct, IPixel { string path = this.GetTestOutputFileName(extension); - + extension = Path.GetExtension(path); IImageFormat format = GetImageFormatByExtension(extension); encoder = encoder ?? format.Encoder; @@ -99,8 +119,8 @@ namespace ImageSharp.Tests 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() From 1da6833a2a51beec93559c282b5ffe5bbf98d4f8 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 24 Mar 2017 08:08:02 +0000 Subject: [PATCH 58/83] fix stylecop issues --- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 2 +- .../Drawing/FillRegionProcessorTests.cs | 2 +- .../Formats/Png/PngEncoderTests.cs | 41 +++---------------- .../Formats/Png/PngSmokeTests.cs | 27 ++++++++++-- tests/ImageSharp.Tests/ImageComparer.cs | 2 +- .../ImageProviders/TestPatternProvider.cs | 32 +++++++-------- 6 files changed, 48 insertions(+), 58 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index e97eaed582..498ae578c3 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -544,7 +544,7 @@ namespace ImageSharp.Formats /// /// The pixel format. /// The containing image data. - /// The image base. + /// The image. private void WritePhysicalChunk(Stream stream, Image image) where TColor : struct, IPixel { diff --git a/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs b/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs index db9686f3d2..03994bc94d 100644 --- a/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs @@ -27,7 +27,7 @@ namespace ImageSharp.Tests.Drawing [InlineData(false, 16, 4)] // we always do 4 sub=pixels when antialising is off. public void MinimumAntialiasSubpixelDepth(bool antialias, int antialiasSubpixelDepth, int expectedAntialiasSubpixelDepth) { - var bounds = new ImageSharp.Rectangle(0, 0, 1, 1); + ImageSharp.Rectangle bounds = new ImageSharp.Rectangle(0, 0, 1, 1); Mock> brush = new Mock>(); Mock region = new Mock(); diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 16906c1fa8..31b14601af 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -8,6 +8,7 @@ using ImageSharp.Formats; namespace ImageSharp.Tests { using System.IO; + using System.Linq; using System.Threading.Tasks; using ImageSharp.IO; using Xunit; @@ -57,10 +58,11 @@ namespace ImageSharp.Tests where TColor : struct, IPixel { using (Image image = provider.GetImage()) - using (EndianBinaryReader reader = Encode(image, null)) + using (MemoryStream ms = new MemoryStream()) { - - byte[] data = reader.ReadBytes(8); + image.Save(ms, new PngEncoder()); + + byte[] data = ms.ToArray().Take(8).ToArray(); byte[] expected = { 0x89, // Set the high bit. 0x50, // P @@ -75,38 +77,5 @@ namespace ImageSharp.Tests Assert.Equal(expected, data); } } - - [Theory] - [WithBlankImages(1, 1, PixelTypes.All)] - [WithBlankImages(10, 10, PixelTypes.StandardImageClass)] - public void WritesFileHeaderHasHeightAndWidth(TestImageProvider provider) - where TColor : struct, IPixel - { - using (Image image = provider.GetImage()) - using (EndianBinaryReader reader = Encode(image, null)) - { - reader.ReadBytes(8); // throw away the file header - uint width = reader.ReadUInt32(); - uint height = reader.ReadUInt32(); - - byte bitDepth = reader.ReadByte(); - byte colorType = reader.ReadByte(); - byte compressionMethod = reader.ReadByte(); - byte filterMethod = reader.ReadByte(); - byte interlaceMethod = reader.ReadByte(); - - Assert.Equal(image.Width, (int)width); - Assert.Equal(image.Height, (int)height); - } - } - - private static EndianBinaryReader Encode(Image img, IEncoderOptions options) - where TColor : struct, IPixel - { - MemoryStream stream = new MemoryStream(); - new PngEncoder().Encode(img, stream, null); - stream.Position = 0; - return new EndianBinaryReader(Endianness.BigEndian, stream); - } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs index 7a15e30e99..d4e8534a7a 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs @@ -18,20 +18,41 @@ namespace ImageSharp.Tests.Formats.Png { [Theory] [WithTestPatternImages(300, 300, PixelTypes.All)] - public void WritesFileMarker(TestImageProvider provider) + 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(provider.Utility.GetTestOutputFileName("bmp")); image.Save(ms, new PngEncoder()); ms.Position = 0; using (Image img2 = new Image(ms, new Configuration(new PngFormat()))) { - img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder()); + // img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder()); + ImageComparer.VisualComparer(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 = new Image(ms, new Configuration(new PngFormat()))) + { + // img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder()); ImageComparer.VisualComparer(image, img2); } } diff --git a/tests/ImageSharp.Tests/ImageComparer.cs b/tests/ImageSharp.Tests/ImageComparer.cs index 091edd3432..ee9359c2af 100644 --- a/tests/ImageSharp.Tests/ImageComparer.cs +++ b/tests/ImageSharp.Tests/ImageComparer.cs @@ -17,7 +17,7 @@ where TColorA : struct, IPixel where TColorB : struct, IPixel { - var percentage = expected.PercentageDifference(actual, segmentThreshold, scalingFactor); + float percentage = expected.PercentageDifference(actual, segmentThreshold, scalingFactor); Assert.InRange(percentage, 0, imageTheshold); } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs index 8d5e77554d..7e4401635b 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs @@ -34,7 +34,7 @@ namespace ImageSharp.Tests { if (!testImages.ContainsKey(this.SourceFileOrDescription)) { - var image = new Image(this.Width, this.Height); + Image image = new Image(this.Width, this.Height); DrawTestPattern(image); testImages.Add(this.SourceFileOrDescription, image); } @@ -46,7 +46,7 @@ namespace ImageSharp.Tests private static void DrawTestPattern(Image image) { // first lets split the image into 4 quadrants - using (var pixels = image.Lock()) + using (PixelAccessor pixels = image.Lock()) { BlackWhiteChecker(pixels); // top left HorizontalLines(pixels); // top right @@ -68,9 +68,9 @@ namespace ImageSharp.Tests NamedColors.Blue }; int p = 0; - for (var y = top; y < bottom; y++) + for (int y = top; y < bottom; y++) { - for (var x = left; x < right; x++) + for (int x = left; x < right; x++) { if (x % stride == 0) { @@ -96,15 +96,15 @@ namespace ImageSharp.Tests }; int p = 0; - for (var y = top; y < bottom; y++) + for (int y = top; y < bottom; y++) { if (y % stride == 0) { p++; p = p % c.Length; } - var pstart = p; - for (var x = left; x < right; x++) + int pstart = p; + for (int x = left; x < right; x++) { if (x % stride == 0) { @@ -132,25 +132,25 @@ namespace ImageSharp.Tests TColor c = default(TColor); - for (var x = left; x < right; x++) + for (int x = left; x < right; x++) { blue.W = red.W = green.W = (float)x / (float)right; c.PackFromVector4(red); - var topBand = top; - for (var y = topBand; y < top + height; y++) + int topBand = top; + for (int y = topBand; y < top + height; y++) { pixels[x, y] = c; } topBand = topBand + height; c.PackFromVector4(green); - for (var y = topBand; y < topBand + height; y++) + for (int y = topBand; y < topBand + height; y++) { pixels[x, y] = c; } topBand = topBand + height; c.PackFromVector4(blue); - for (var y = topBand; y < bottom; y++) + for (int y = topBand; y < bottom; y++) { pixels[x, y] = c; } @@ -168,12 +168,12 @@ namespace ImageSharp.Tests uint stepsPerPixel = (uint)(uint.MaxValue / pixelCount); TColor c = default(TColor); Color t = new Color(0); - uint inital = 0; - for (var x = left; x < right; x++) - for (var y = top; y < bottom; y++) + + for (int x = left; x < right; x++) + for (int y = top; y < bottom; y++) { t.PackedValue += stepsPerPixel; - var v = t.ToVector4(); + Vector4 v = t.ToVector4(); //v.W = (x - left) / (float)left; c.PackFromVector4(v); pixels[x, y] = c; From fb0ecdbe0ca962f6c764ad453b62f6b3485b93dd Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 24 Mar 2017 18:57:38 +0000 Subject: [PATCH 59/83] Add explanatory comments --- tests/ImageSharp.Tests/ImageComparer.cs | 41 +++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/tests/ImageSharp.Tests/ImageComparer.cs b/tests/ImageSharp.Tests/ImageComparer.cs index ee9359c2af..0462506a65 100644 --- a/tests/ImageSharp.Tests/ImageComparer.cs +++ b/tests/ImageSharp.Tests/ImageComparer.cs @@ -9,10 +9,29 @@ /// public static class ImageComparer { - const int DefaultScalingFactor = 32; - const int DefaultSegmentThreshold = 3; - const float DefaultImageThreshold = 0.000f; + 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 VisualComparer(Image expected, Image actual, float imageTheshold = DefaultImageThreshold, byte segmentThreshold = DefaultSegmentThreshold, int scalingFactor = DefaultScalingFactor) where TColorA : struct, IPixel where TColorB : struct, IPixel @@ -22,6 +41,22 @@ 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 From d6c31d95b161a1246218d8bde91efa44131f8144 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sat, 25 Mar 2017 07:07:58 +0000 Subject: [PATCH 60/83] Improve method name Added comments too --- .../Formats/Png/PngSmokeTests.cs | 6 +-- tests/ImageSharp.Tests/ImageComparer.cs | 2 +- .../ImageProviders/TestPatternProvider.cs | 39 +++++++++++++++---- 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs index d4e8534a7a..9249030398 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs @@ -32,7 +32,7 @@ namespace ImageSharp.Tests.Formats.Png using (Image img2 = new Image(ms, new Configuration(new PngFormat()))) { // img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder()); - ImageComparer.VisualComparer(image, img2); + ImageComparer.CheckSimilarity(image, img2); } } } @@ -53,7 +53,7 @@ namespace ImageSharp.Tests.Formats.Png using (Image img2 = new Image(ms, new Configuration(new PngFormat()))) { // img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder()); - ImageComparer.VisualComparer(image, img2); + ImageComparer.CheckSimilarity(image, img2); } } } @@ -75,7 +75,7 @@ namespace ImageSharp.Tests.Formats.Png ms.Position = 0; using (Image img2 = new Image(ms, new Configuration(new PngFormat()))) { - ImageComparer.VisualComparer(image, img2); + ImageComparer.CheckSimilarity(image, img2); } } } diff --git a/tests/ImageSharp.Tests/ImageComparer.cs b/tests/ImageSharp.Tests/ImageComparer.cs index 0462506a65..41b884dd43 100644 --- a/tests/ImageSharp.Tests/ImageComparer.cs +++ b/tests/ImageSharp.Tests/ImageComparer.cs @@ -32,7 +32,7 @@ /// This is a sampling factor we sample a grid of average pixels width by high /// The default undefined value is /// - public static void VisualComparer(Image expected, Image actual, float imageTheshold = DefaultImageThreshold, byte segmentThreshold = DefaultSegmentThreshold, int scalingFactor = DefaultScalingFactor) + 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 { diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs index 7e4401635b..89f46a947e 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs @@ -12,6 +12,11 @@ namespace ImageSharp.Tests public abstract partial class TestImageProvider where TColor : struct, IPixel { + + /// + /// A test image provider that produces test patterns. + /// + /// private class TestPatternProvider : TestImageProvider { static Dictionary> testImages = new Dictionary>(); @@ -43,19 +48,26 @@ namespace ImageSharp.Tests } } + /// + /// 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 - HorizontalLines(pixels); // top right + VirticalBars(pixels); // top right TransparentGradients(pixels); // bottom left - Raninbow(pixels); // bottom right + Rainbow(pixels); // bottom right } } - - private static void HorizontalLines(PixelAccessor pixels) + /// + /// Fills the top right quadrant with alternating solid vertical bars. + /// + /// + private static void VirticalBars(PixelAccessor pixels) { // topLeft int left = pixels.Width / 2; @@ -82,6 +94,10 @@ namespace ImageSharp.Tests } } + /// + /// fills the top left quadrant with a black and white checker board. + /// + /// private static void BlackWhiteChecker(PixelAccessor pixels) { // topLeft @@ -116,7 +132,11 @@ namespace ImageSharp.Tests 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 @@ -155,9 +175,14 @@ namespace ImageSharp.Tests pixels[x, y] = c; } } - } - private static void Raninbow(PixelAccessor pixels) + + /// + /// 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; From 9ec4ae84bb1f53682a9ff0d3034bf0d26887c3c9 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sat, 25 Mar 2017 07:28:02 +0000 Subject: [PATCH 61/83] make constructor public --- src/ImageSharp/Image.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs index 352107021a..195af60563 100644 --- a/src/ImageSharp/Image.cs +++ b/src/ImageSharp/Image.cs @@ -49,7 +49,7 @@ namespace ImageSharp /// /// The other image, where the clone should be made from. /// is null. - internal Image(Image other) + public Image(Image other) : base(other) { } From 2e4c1cd1b0e59516aea5d47dfe5aa102befbe7e8 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sat, 25 Mar 2017 07:28:35 +0000 Subject: [PATCH 62/83] Clean up usings and add comments --- src/ImageSharp/Image.Decode.cs | 10 +++++++--- src/ImageSharp/Image.FromBytes.cs | 4 ---- src/ImageSharp/Image.FromFile.cs | 6 +----- src/ImageSharp/Image.FromStream.cs | 16 +++++++--------- 4 files changed, 15 insertions(+), 21 deletions(-) diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index 32943ead58..a86a6e948d 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -5,12 +5,9 @@ namespace ImageSharp { - using System; using System.Buffers; - using System.Diagnostics; using System.IO; using System.Linq; - using System.Text; using Formats; /// @@ -19,8 +16,15 @@ namespace ImageSharp /// 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) { diff --git a/src/ImageSharp/Image.FromBytes.cs b/src/ImageSharp/Image.FromBytes.cs index 8e0ac04a5a..d23d0baeed 100644 --- a/src/ImageSharp/Image.FromBytes.cs +++ b/src/ImageSharp/Image.FromBytes.cs @@ -6,11 +6,7 @@ namespace ImageSharp { using System; - using System.Buffers; - using System.Diagnostics; using System.IO; - using System.Linq; - using System.Text; using Formats; /// diff --git a/src/ImageSharp/Image.FromFile.cs b/src/ImageSharp/Image.FromFile.cs index b7fb0eef59..1710909e18 100644 --- a/src/ImageSharp/Image.FromFile.cs +++ b/src/ImageSharp/Image.FromFile.cs @@ -5,15 +5,11 @@ namespace ImageSharp { +#if !NETSTANDARD1_1 using System; - using System.Buffers; - using System.Diagnostics; using System.IO; - using System.Linq; - using System.Text; using Formats; -#if !NETSTANDARD1_1 /// /// 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. diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index e995c47997..16793e16c7 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -6,10 +6,7 @@ namespace ImageSharp { using System; - using System.Buffers; - using System.Diagnostics; using System.IO; - using System.Linq; using System.Text; using Formats; @@ -86,7 +83,9 @@ namespace ImageSharp /// The image public static Image Load(Stream stream, IDecoderOptions options, Configuration config) { - return new Image(Load(stream, options, config)); + Image image = Load(stream, options, config); + + return image as Image ?? new Image(image); } /// @@ -101,7 +100,9 @@ namespace ImageSharp /// The image public static Image Load(Stream stream, IImageDecoder decoder, IDecoderOptions options) { - return new Image(Load(stream, decoder, options)); + Image image = new Image(Load(stream, decoder, options)); + + return image as Image ?? new Image(image); } /// @@ -200,10 +201,7 @@ namespace ImageSharp { config = config ?? Configuration.Default; - Image img = WithSeekableStream(stream, s => - { - return Decode(stream, options, config); - }); + Image img = WithSeekableStream(stream, s => Decode(stream, options, config)); if (img != null) { From 0aa20b66e9ff854ac7e841a943dfa2b776b2ffbf Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 26 Mar 2017 09:31:45 +0100 Subject: [PATCH 63/83] Move metadata loading into constructor/creator. --- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 3 +- .../Formats/Jpeg/JpegDecoderCore.cs | 4 +- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 3 +- src/ImageSharp/Image.Create.cs | 26 ++++++++-- src/ImageSharp/Image.cs | 15 ++++++ src/ImageSharp/Image/Image{TColor}.cs | 34 ++++++++++---- src/ImageSharp/MetaData/ImageMetaData.cs | 47 ++++++++----------- 7 files changed, 85 insertions(+), 47 deletions(-) diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 79348a7aba..4c119ca737 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -362,8 +362,7 @@ namespace ImageSharp.Formats this.metaData.Quality = colorTableLength / 3; // This initializes the image to become fully transparent because the alpha channel is zero. - this.image = Image.Create(imageWidth, imageHeight, this.configuration); - this.image.MetaData.LoadFrom(this.metaData); + this.image = Image.Create(imageWidth, imageHeight, this.metaData, this.configuration); this.SetFrameDelay(this.metaData); diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index fc9506b546..33533aa12b 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -505,9 +505,7 @@ namespace ImageSharp.Formats private Image ConvertJpegPixelsToImagePixels(ImageMetaData metadata) where TColor : struct, IPixel { - Image image = Image.Create(this.ImageWidth, this.ImageHeight, this.configuration); - - image.MetaData.LoadFrom(metadata); + Image image = Image.Create(this.ImageWidth, this.ImageHeight, metadata, this.configuration); if (this.grayImage.IsInitialized) { diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 7705e95492..d6529940e2 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -220,8 +220,7 @@ namespace ImageSharp.Formats 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 image = Image.Create(this.header.Width, this.header.Height, this.configuration); - image.MetaData.LoadFrom(metadata); + Image image = Image.Create(this.header.Width, this.header.Height, metadata, this.configuration); using (PixelAccessor pixels = image.Lock()) { diff --git a/src/ImageSharp/Image.Create.cs b/src/ImageSharp/Image.Create.cs index bc430724a6..fcecefd7b7 100644 --- a/src/ImageSharp/Image.Create.cs +++ b/src/ImageSharp/Image.Create.cs @@ -24,23 +24,43 @@ namespace ImageSharp /// 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, Configuration configuration) + 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, configuration) as Image; + return new Image(width, height, metadata, configuration) as Image; } else { - return new Image(width, height, configuration); + 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.cs b/src/ImageSharp/Image.cs index 195af60563..00688afc96 100644 --- a/src/ImageSharp/Image.cs +++ b/src/ImageSharp/Image.cs @@ -53,5 +53,20 @@ namespace ImageSharp : base(other) { } + + /// + /// 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 metadata. + /// + /// The configuration providing initialization code which allows extending the library. + /// + internal Image(int width, int height, ImageMetaData metadata, Configuration configuration) + : base(width, height, metadata, configuration) + { + } } } diff --git a/src/ImageSharp/Image/Image{TColor}.cs b/src/ImageSharp/Image/Image{TColor}.cs index 82991948cb..d063c3ff16 100644 --- a/src/ImageSharp/Image/Image{TColor}.cs +++ b/src/ImageSharp/Image/Image{TColor}.cs @@ -36,15 +36,8 @@ namespace ImageSharp /// The configuration providing initialization code which allows extending the library. /// public Image(int width, int height, Configuration configuration) - : base(width, height, configuration) + : this(width, height, new ImageMetaData(), configuration) { - if (!this.Configuration.ImageFormats.Any()) - { - throw new InvalidOperationException("No image formats have been configured."); - } - - this.MetaData = new ImageMetaData(); - this.CurrentImageFormat = this.Configuration.ImageFormats.First(); } /// @@ -87,12 +80,35 @@ namespace ImageSharp public Image(ImageBase other) : base(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 diff --git a/src/ImageSharp/MetaData/ImageMetaData.cs b/src/ImageSharp/MetaData/ImageMetaData.cs index d7d5e88b5f..aed6efa2cb 100644 --- a/src/ImageSharp/MetaData/ImageMetaData.cs +++ b/src/ImageSharp/MetaData/ImageMetaData.cs @@ -48,7 +48,25 @@ namespace ImageSharp { DebugGuard.NotNull(other, nameof(other)); - this.LoadFrom(other); + this.HorizontalResolution = other.HorizontalResolution; + this.VerticalResolution = other.VerticalResolution; + this.Quality = other.Quality; + this.FrameDelay = other.FrameDelay; + this.RepeatCount = other.RepeatCount; + + foreach (ImageProperty property in other.Properties) + { + this.Properties.Add(new ImageProperty(property)); + } + + if (other.ExifProfile != null) + { + this.ExifProfile = new ExifProfile(other.ExifProfile); + } + else + { + this.ExifProfile = null; + } } /// @@ -130,32 +148,5 @@ namespace ImageSharp { this.ExifProfile?.Sync(this); } - - /// - /// Sets the current metadata values based on a previous metadata object. - /// - /// Meta data object to copy values from. - internal void LoadFrom(ImageMetaData other) - { - this.HorizontalResolution = other.HorizontalResolution; - this.VerticalResolution = other.VerticalResolution; - this.Quality = other.Quality; - this.FrameDelay = other.FrameDelay; - this.RepeatCount = other.RepeatCount; - - foreach (ImageProperty property in other.Properties) - { - this.Properties.Add(new ImageProperty(property)); - } - - if (other.ExifProfile != null) - { - this.ExifProfile = new ExifProfile(other.ExifProfile); - } - else - { - this.ExifProfile = null; - } - } } } From c1a061194a72ae4dd76e4017ad9182aa6167875d Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 26 Mar 2017 12:44:23 +0100 Subject: [PATCH 64/83] Move config argument to be first argument --- src/ImageSharp/Image.FromBytes.cs | 32 +++++++++---------- src/ImageSharp/Image.FromFile.cs | 32 +++++++++---------- src/ImageSharp/Image.FromStream.cs | 30 ++++++++--------- .../ImageSharp.Tests/Image/ImageLoadTests.cs | 24 +++++++------- 4 files changed, 59 insertions(+), 59 deletions(-) diff --git a/src/ImageSharp/Image.FromBytes.cs b/src/ImageSharp/Image.FromBytes.cs index d23d0baeed..b2f9854f22 100644 --- a/src/ImageSharp/Image.FromBytes.cs +++ b/src/ImageSharp/Image.FromBytes.cs @@ -25,7 +25,7 @@ namespace ImageSharp /// The image public static Image Load(byte[] data) { - return Load(data, null, (Configuration)null); + return Load(null, data, null); } /// @@ -39,21 +39,21 @@ namespace ImageSharp /// The image public static Image Load(byte[] data, IDecoderOptions options) { - return Load(data, options, null); + return Load(null, data, options); } /// /// Loads the image from the given byte array. /// - /// The byte array containing image data. /// 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(byte[] data, Configuration config) + public static Image Load(Configuration config, byte[] data) { - return Load(data, null, config); + return Load(config, data, null); } /// @@ -73,18 +73,18 @@ namespace ImageSharp /// /// Loads the image from the given byte array. /// + /// The configuration options. /// The byte array containing image data. /// The options for the decoder. - /// The configuration options. /// /// Thrown if the stream is not readable nor seekable. /// /// The image - public static Image Load(byte[] data, IDecoderOptions options, Configuration config) + public static Image Load(Configuration config, byte[] data, IDecoderOptions options) { using (MemoryStream ms = new MemoryStream(data)) { - return Load(ms, options, config); + return Load(config, ms, options); } } @@ -118,7 +118,7 @@ namespace ImageSharp public static Image Load(byte[] data) where TColor : struct, IPixel { - return Load(data, null, (Configuration)null); + return Load(null, data, null); } /// @@ -134,23 +134,23 @@ namespace ImageSharp public static Image Load(byte[] data, IDecoderOptions options) where TColor : struct, IPixel { - return Load(data, options, null); + return Load(null, data, options); } /// /// Loads the image from the given byte array. /// /// The pixel format. - /// The byte array containing image data. /// 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(byte[] data, Configuration config) + public static Image Load(Configuration config, byte[] data) where TColor : struct, IPixel { - return Load(data, null, config); + return Load(config, data, null); } /// @@ -173,19 +173,19 @@ namespace ImageSharp /// 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. - /// The configuration options. /// /// Thrown if the stream is not readable nor seekable. /// /// The image - public static Image Load(byte[] data, IDecoderOptions options, Configuration config) + public static Image Load(Configuration config, byte[] data, IDecoderOptions options) where TColor : struct, IPixel { using (MemoryStream ms = new MemoryStream(data)) { - return Load(ms, options, config); + return Load(config, ms, options); } } diff --git a/src/ImageSharp/Image.FromFile.cs b/src/ImageSharp/Image.FromFile.cs index 1710909e18..40cdfe3eff 100644 --- a/src/ImageSharp/Image.FromFile.cs +++ b/src/ImageSharp/Image.FromFile.cs @@ -26,7 +26,7 @@ namespace ImageSharp /// The image public static Image Load(string path) { - return Load(path, null, (Configuration)null); + return Load(null, path, null); } /// @@ -40,21 +40,21 @@ namespace ImageSharp /// The image public static Image Load(string path, IDecoderOptions options) { - return Load(path, options, null); + return Load(null, path, options); } /// /// Loads the image from the given file. /// - /// The file path to the image. /// 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(string path, Configuration config) + public static Image Load(Configuration config, string path) { - return Load(path, null, config); + return Load(config, path, null); } /// @@ -89,19 +89,19 @@ namespace ImageSharp /// /// Loads the image from the given file. /// + /// The configuration options. /// The file path to the image. /// The options for the decoder. - /// The configuration options. /// /// Thrown if the stream is not readable nor seekable. /// /// The image - public static Image Load(string path, IDecoderOptions options, Configuration config) + public static Image Load(Configuration config, string path, IDecoderOptions options) { config = config ?? Configuration.Default; using (Stream s = config.FileSystem.OpenRead(path)) { - return Load(s, options, config); + return Load(config, s, options); } } @@ -117,7 +117,7 @@ namespace ImageSharp public static Image Load(string path) where TColor : struct, IPixel { - return Load(path, null, (Configuration)null); + return Load(null, path, null); } /// @@ -133,23 +133,23 @@ namespace ImageSharp public static Image Load(string path, IDecoderOptions options) where TColor : struct, IPixel { - return Load(path, options, null); + return Load(null, path, options); } /// /// Loads the image from the given file. /// /// The pixel format. - /// The file path to the image. /// 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(string path, Configuration config) + public static Image Load(Configuration config, string path) where TColor : struct, IPixel { - return Load(path, null, config); + return Load(config, path, null); } /// @@ -172,20 +172,20 @@ namespace ImageSharp /// Loads the image from the given file. /// /// The pixel format. + /// The configuration options. /// The file path to the image. /// The options for the decoder. - /// The configuration options. /// /// Thrown if the stream is not readable nor seekable. /// /// The image - public static Image Load(string path, IDecoderOptions options, Configuration config) + 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(s, options, config); + return Load(config, s, options); } } diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index 16793e16c7..be807aef44 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -26,7 +26,7 @@ namespace ImageSharp /// The image public static Image Load(Stream stream) { - return Load(stream, null, (Configuration)null); + return Load(null, stream, null); } /// @@ -40,21 +40,21 @@ namespace ImageSharp /// The image public static Image Load(Stream stream, IDecoderOptions options) { - return Load(stream, options, null); + return Load(null, stream, options); } /// /// Loads the image from the given stream. /// - /// The stream containing image information. /// 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(Stream stream, Configuration config) + public static Image Load(Configuration config, Stream stream) { - return Load(stream, null, config); + return Load(config, stream, null); } /// @@ -74,16 +74,16 @@ namespace ImageSharp /// /// Loads the image from the given stream. /// + /// The configuration options. /// The stream containing image information. /// The options for the decoder. - /// The configuration options. /// /// Thrown if the stream is not readable nor seekable. /// /// The image - public static Image Load(Stream stream, IDecoderOptions options, Configuration config) + public static Image Load(Configuration config, Stream stream, IDecoderOptions options) { - Image image = Load(stream, options, config); + Image image = Load(config, stream, options); return image as Image ?? new Image(image); } @@ -117,7 +117,7 @@ namespace ImageSharp public static Image Load(Stream stream) where TColor : struct, IPixel { - return Load(stream, null, (Configuration)null); + return Load(null, stream, null); } /// @@ -133,23 +133,23 @@ namespace ImageSharp public static Image Load(Stream stream, IDecoderOptions options) where TColor : struct, IPixel { - return Load(stream, options, null); + return Load(null, stream, options); } /// /// Loads the image from the given stream. /// /// The pixel format. - /// The stream containing image information. /// 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(Stream stream, Configuration config) + public static Image Load(Configuration config, Stream stream) where TColor : struct, IPixel { - return Load(stream, null, config); + return Load(config, stream, null); } /// @@ -189,14 +189,14 @@ namespace ImageSharp /// Loads the image from the given stream. /// /// The pixel format. + /// The configuration options. /// The stream containing image information. /// The options for the decoder. - /// The configuration options. /// /// Thrown if the stream is not readable nor seekable. /// /// The image - public static Image Load(Stream stream, IDecoderOptions options, Configuration config) + public static Image Load(Configuration config, Stream stream, IDecoderOptions options) where TColor : struct, IPixel { config = config ?? Configuration.Default; diff --git a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs index 52b8f6ee28..91b907e192 100644 --- a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs @@ -128,7 +128,7 @@ namespace ImageSharp.Tests public void LoadFromStreamWithConfig() { Stream stream = new MemoryStream(); - Image img = Image.Load(stream, this.LocalConfiguration); + Image img = Image.Load(this.LocalConfiguration, stream); Assert.NotNull(img); Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); @@ -141,7 +141,7 @@ namespace ImageSharp.Tests public void LoadFromStreamWithTypeAndConfig() { Stream stream = new MemoryStream(); - Image img = Image.Load(stream, this.LocalConfiguration); + Image img = Image.Load(this.LocalConfiguration, stream); Assert.NotNull(img); Assert.Equal(this.returnImage, img); @@ -155,7 +155,7 @@ namespace ImageSharp.Tests public void LoadFromStreamWithConfigAndOptions() { Stream stream = new MemoryStream(); - Image img = Image.Load(stream, this.decoderOptions, this.LocalConfiguration); + Image img = Image.Load(this.LocalConfiguration, stream, this.decoderOptions); Assert.NotNull(img); Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); @@ -168,7 +168,7 @@ namespace ImageSharp.Tests public void LoadFromStreamWithTypeAndConfigAndOptions() { Stream stream = new MemoryStream(); - Image img = Image.Load(stream, this.decoderOptions, this.LocalConfiguration); + Image img = Image.Load(this.LocalConfiguration, stream, this.decoderOptions); Assert.NotNull(img); Assert.Equal(this.returnImage, img); @@ -276,7 +276,7 @@ namespace ImageSharp.Tests [Fact] public void LoadFromBytesWithConfig() { - Image img = Image.Load(this.DataStream.ToArray(), this.LocalConfiguration); + Image img = Image.Load(this.LocalConfiguration, this.DataStream.ToArray()); Assert.NotNull(img); Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); @@ -289,7 +289,7 @@ namespace ImageSharp.Tests [Fact] public void LoadFromBytesWithTypeAndConfig() { - Image img = Image.Load(this.DataStream.ToArray(), this.LocalConfiguration); + Image img = Image.Load(this.LocalConfiguration, this.DataStream.ToArray()); Assert.NotNull(img); Assert.Equal(this.returnImage, img); @@ -304,7 +304,7 @@ namespace ImageSharp.Tests [Fact] public void LoadFromBytesWithConfigAndOptions() { - Image img = Image.Load(this.DataStream.ToArray(), this.decoderOptions, this.LocalConfiguration); + Image img = Image.Load(this.LocalConfiguration, this.DataStream.ToArray(), this.decoderOptions); Assert.NotNull(img); Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); @@ -317,7 +317,7 @@ namespace ImageSharp.Tests [Fact] public void LoadFromBytesWithTypeAndConfigAndOptions() { - Image img = Image.Load(this.DataStream.ToArray(), this.decoderOptions, this.LocalConfiguration); + Image img = Image.Load(this.LocalConfiguration, this.DataStream.ToArray(), this.decoderOptions); Assert.NotNull(img); Assert.Equal(this.returnImage, img); @@ -425,7 +425,7 @@ namespace ImageSharp.Tests [Fact] public void LoadFromFileWithConfig() { - Image img = Image.Load(this.FilePath, this.LocalConfiguration); + Image img = Image.Load(this.LocalConfiguration, this.FilePath); Assert.NotNull(img); Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); @@ -437,7 +437,7 @@ namespace ImageSharp.Tests [Fact] public void LoadFromFileWithTypeAndConfig() { - Image img = Image.Load(this.FilePath, this.LocalConfiguration); + Image img = Image.Load(this.LocalConfiguration, this.FilePath); Assert.NotNull(img); Assert.Equal(this.returnImage, img); @@ -450,7 +450,7 @@ namespace ImageSharp.Tests [Fact] public void LoadFromFileWithConfigAndOptions() { - Image img = Image.Load(this.FilePath, this.decoderOptions, this.LocalConfiguration); + Image img = Image.Load(this.LocalConfiguration, this.FilePath, this.decoderOptions); Assert.NotNull(img); Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); @@ -462,7 +462,7 @@ namespace ImageSharp.Tests [Fact] public void LoadFromFileWithTypeAndConfigAndOptions() { - Image img = Image.Load(this.FilePath, this.decoderOptions, this.LocalConfiguration); + Image img = Image.Load(this.LocalConfiguration, this.FilePath, this.decoderOptions); Assert.NotNull(img); Assert.Equal(this.returnImage, img); From a8901f3c9cec097c850b7c8ff989ca07eb80318f Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 26 Mar 2017 15:01:58 +0100 Subject: [PATCH 65/83] Update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 576ed76d07..86ce5ddd4d 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,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 +85,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() From 79ca3c430fde8387540888d0c4787848da748286 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 26 Mar 2017 15:02:13 +0100 Subject: [PATCH 66/83] Increment version --- src/ImageSharp.Drawing/ImageSharp.Drawing.csproj | 2 +- src/ImageSharp/ImageSharp.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj index e15bfe74ba..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-alpha4 + 1.0.0-alpha5 James Jackson-South and contributors netstandard1.1 true diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 63cb081c33..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-alpha4 + 1.0.0-alpha5 James Jackson-South and contributors netstandard1.3;netstandard1.1 true From da1dffd5cccfb634cdfec66518a07d1a48751d4e Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 26 Mar 2017 15:12:14 +0100 Subject: [PATCH 67/83] decoder configuration argument order --- ImageSharp.sln | 3 ++ src/ImageSharp/Formats/Bmp/BmpDecoder.cs | 2 +- src/ImageSharp/Formats/Gif/GifDecoder.cs | 8 +-- src/ImageSharp/Formats/IImageDecoder.cs | 4 +- src/ImageSharp/Formats/Jpeg/JpegDecoder.cs | 2 +- src/ImageSharp/Formats/Png/PngDecoder.cs | 8 +-- src/ImageSharp/Image.Decode.cs | 2 +- src/ImageSharp/Image.FromStream.cs | 2 +- src/README.md | 7 +++ .../ImageSharp.Tests/Image/ImageLoadTests.cs | 50 +++++++++---------- tests/ImageSharp.Tests/TestFormat.cs | 2 +- 11 files changed, 50 insertions(+), 40 deletions(-) create mode 100644 src/README.md diff --git a/ImageSharp.sln b/ImageSharp.sln index 628fa70153..6e389421e7 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -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/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index 8414807e0c..da5d246372 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -26,7 +26,7 @@ namespace ImageSharp.Formats public class BmpDecoder : IImageDecoder { /// - public Image Decode(Stream stream, IDecoderOptions options, Configuration configuration) + public Image Decode(Configuration configuration, Stream stream, IDecoderOptions options) where TColor : struct, IPixel { diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index 2e56b0baba..2eb89de8ff 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -14,24 +14,24 @@ namespace ImageSharp.Formats public class GifDecoder : IImageDecoder { /// - public Image Decode(Stream stream, IDecoderOptions options, Configuration configuration) + public Image Decode(Configuration configuration, Stream stream, IDecoderOptions options) where TColor : struct, IPixel { IGifDecoderOptions gifOptions = GifDecoderOptions.Create(options); - return this.Decode(stream, gifOptions, configuration); + return this.Decode(configuration, stream, gifOptions); } /// /// Decodes the image from the specified stream to the . /// /// The pixel format. + /// The configuration. /// The containing image data. /// The options for the decoder. - /// The configuration. /// The image thats been decoded. - public Image Decode(Stream stream, IGifDecoderOptions options, Configuration configuration) + public Image Decode(Configuration configuration, Stream stream, IGifDecoderOptions options) where TColor : struct, IPixel { return new GifDecoderCore(options, configuration).Decode(stream); diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs index 8da23d499f..c85fbef101 100644 --- a/src/ImageSharp/Formats/IImageDecoder.cs +++ b/src/ImageSharp/Formats/IImageDecoder.cs @@ -17,11 +17,11 @@ namespace ImageSharp.Formats /// Decodes the image from the specified stream to the . /// /// The pixel format. + /// The configuration for the image. /// The containing image data. /// The options for the decoder. - /// The configuration for the image. /// The decoded image - Image Decode(Stream stream, IDecoderOptions options, Configuration configuration) + Image Decode(Configuration configuration, Stream stream, IDecoderOptions options) where TColor : struct, IPixel; } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index 254506af05..0aac316035 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Formats public class JpegDecoder : IImageDecoder { /// - public Image Decode(Stream stream, IDecoderOptions options, Configuration configuration) + public Image Decode(Configuration configuration, Stream stream, IDecoderOptions options) where TColor : struct, IPixel { Guard.NotNull(stream, "stream"); diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index bf17ad1428..d0a820c17f 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -31,24 +31,24 @@ namespace ImageSharp.Formats public class PngDecoder : IImageDecoder { /// - public Image Decode(Stream stream, IDecoderOptions options, Configuration configuration) + public Image Decode(Configuration configuration, Stream stream, IDecoderOptions options) where TColor : struct, IPixel { IPngDecoderOptions pngOptions = PngDecoderOptions.Create(options); - return this.Decode(stream, pngOptions, configuration); + return this.Decode(configuration, stream, pngOptions); } /// /// Decodes the image from the specified stream to the . /// /// The pixel format. + /// The configuration for the image. /// The containing image data. /// The options for the decoder. - /// The configuration for the image. /// The decoded image. - public Image Decode(Stream stream, IPngDecoderOptions options, Configuration configuration) + public Image Decode(Configuration configuration, Stream stream, IPngDecoderOptions options) where TColor : struct, IPixel { return new PngDecoderCore(options, configuration).Decode(stream); diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index a86a6e948d..c1c1371220 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -67,7 +67,7 @@ namespace ImageSharp return null; } - Image img = format.Decoder.Decode(stream, options, config); + Image img = format.Decoder.Decode(config, stream, options); img.CurrentImageFormat = format; return img; } diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index be807aef44..41ac7757e7 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -182,7 +182,7 @@ namespace ImageSharp public static Image Load(Stream stream, IImageDecoder decoder, IDecoderOptions options) where TColor : struct, IPixel { - return WithSeekableStream(stream, s => decoder.Decode(s, options, Configuration.Default)); + return WithSeekableStream(stream, s => decoder.Decode(Configuration.Default, s, options)); } /// 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.Tests/Image/ImageLoadTests.cs b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs index 91b907e192..a7f57ca1c3 100644 --- a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs @@ -44,7 +44,7 @@ namespace ImageSharp.Tests 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())) + this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny(), It.IsAny())) .Callback((s, o, c) => { using (var ms = new MemoryStream()) @@ -133,7 +133,7 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - this.localDecoder.Verify(x => x.Decode(stream, null, this.LocalConfiguration)); + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, stream, null)); } @@ -147,7 +147,7 @@ namespace ImageSharp.Tests Assert.Equal(this.returnImage, img); Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - this.localDecoder.Verify(x => x.Decode(stream, null, this.LocalConfiguration)); + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, stream, null)); } @@ -160,7 +160,7 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - this.localDecoder.Verify(x => x.Decode(stream, this.decoderOptions, this.LocalConfiguration)); + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, stream, this.decoderOptions)); } @@ -174,7 +174,7 @@ namespace ImageSharp.Tests Assert.Equal(this.returnImage, img); Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - this.localDecoder.Verify(x => x.Decode(stream, this.decoderOptions, this.LocalConfiguration)); + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, stream, this.decoderOptions)); } @@ -187,7 +187,7 @@ namespace ImageSharp.Tests Image img = Image.Load(stream, this.localDecoder.Object); Assert.NotNull(img); - this.localDecoder.Verify(x => x.Decode(stream, null, Configuration.Default)); + this.localDecoder.Verify(x => x.Decode(Configuration.Default, stream, null)); } [Fact] @@ -198,7 +198,7 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.returnImage, img); - this.localDecoder.Verify(x => x.Decode(stream, null, Configuration.Default)); + this.localDecoder.Verify(x => x.Decode(Configuration.Default, stream, null)); } [Fact] @@ -208,7 +208,7 @@ namespace ImageSharp.Tests Image img = Image.Load(stream, this.localDecoder.Object, this.decoderOptions); Assert.NotNull(img); - this.localDecoder.Verify(x => x.Decode(stream, this.decoderOptions, Configuration.Default)); + this.localDecoder.Verify(x => x.Decode(Configuration.Default, stream, this.decoderOptions)); } [Fact] @@ -219,7 +219,7 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.returnImage, img); - this.localDecoder.Verify(x => x.Decode(stream, this.decoderOptions, Configuration.Default)); + this.localDecoder.Verify(x => x.Decode(Configuration.Default, stream, this.decoderOptions)); } [Fact] @@ -281,7 +281,7 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - this.localDecoder.Verify(x => x.Decode(It.IsAny(), null, this.LocalConfiguration)); + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, It.IsAny(), null)); Assert.Equal(this.DataStream.ToArray(), this.DecodedData); } @@ -296,7 +296,7 @@ namespace ImageSharp.Tests Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - this.localDecoder.Verify(x => x.Decode(It.IsAny(), null, this.LocalConfiguration)); + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, It.IsAny(), null)); Assert.Equal(this.DataStream.ToArray(), this.DecodedData); } @@ -309,7 +309,7 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - this.localDecoder.Verify(x => x.Decode(It.IsAny(), this.decoderOptions, this.LocalConfiguration)); + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, It.IsAny(), this.decoderOptions)); Assert.Equal(this.DataStream.ToArray(), this.DecodedData); } @@ -323,7 +323,7 @@ namespace ImageSharp.Tests Assert.Equal(this.returnImage, img); Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - this.localDecoder.Verify(x => x.Decode(It.IsAny(), this.decoderOptions, this.LocalConfiguration)); + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, It.IsAny(), this.decoderOptions)); Assert.Equal(this.DataStream.ToArray(), this.DecodedData); } @@ -335,7 +335,7 @@ namespace ImageSharp.Tests Image img = Image.Load(this.DataStream.ToArray(), this.localDecoder.Object); Assert.NotNull(img); - this.localDecoder.Verify(x => x.Decode(It.IsAny(), null, Configuration.Default)); + this.localDecoder.Verify(x => x.Decode(Configuration.Default, It.IsAny(), null)); Assert.Equal(this.DataStream.ToArray(), this.DecodedData); } @@ -346,7 +346,7 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.returnImage, img); - this.localDecoder.Verify(x => x.Decode(It.IsAny(), null, Configuration.Default)); + this.localDecoder.Verify(x => x.Decode(Configuration.Default, It.IsAny(), null)); Assert.Equal(this.DataStream.ToArray(), this.DecodedData); } @@ -356,7 +356,7 @@ namespace ImageSharp.Tests Image img = Image.Load(this.DataStream.ToArray(), this.localDecoder.Object, this.decoderOptions); Assert.NotNull(img); - this.localDecoder.Verify(x => x.Decode(It.IsAny(), this.decoderOptions, Configuration.Default)); + this.localDecoder.Verify(x => x.Decode(Configuration.Default, It.IsAny(), this.decoderOptions)); Assert.Equal(this.DataStream.ToArray(), this.DecodedData); } @@ -367,7 +367,7 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.returnImage, img); - this.localDecoder.Verify(x => x.Decode(It.IsAny(), this.decoderOptions, Configuration.Default)); + this.localDecoder.Verify(x => x.Decode(Configuration.Default, It.IsAny(), this.decoderOptions)); Assert.Equal(this.DataStream.ToArray(), this.DecodedData); } @@ -430,7 +430,7 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - this.localDecoder.Verify(x => x.Decode(this.DataStream, null, this.LocalConfiguration)); + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, this.DataStream, null)); } @@ -443,7 +443,7 @@ namespace ImageSharp.Tests Assert.Equal(this.returnImage, img); Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - this.localDecoder.Verify(x => x.Decode(this.DataStream, null, this.LocalConfiguration)); + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, this.DataStream, null)); } @@ -455,7 +455,7 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - this.localDecoder.Verify(x => x.Decode(this.DataStream, this.decoderOptions, this.LocalConfiguration)); + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, this.DataStream, this.decoderOptions)); } @@ -468,7 +468,7 @@ namespace ImageSharp.Tests Assert.Equal(this.returnImage, img); Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - this.localDecoder.Verify(x => x.Decode(this.DataStream, this.decoderOptions, this.LocalConfiguration)); + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, this.DataStream, this.decoderOptions)); } @@ -479,7 +479,7 @@ namespace ImageSharp.Tests Image img = Image.Load(this.FilePath, this.localDecoder.Object); Assert.NotNull(img); - this.localDecoder.Verify(x => x.Decode(this.DataStream, null, Configuration.Default)); + this.localDecoder.Verify(x => x.Decode(Configuration.Default, this.DataStream, null)); } [Fact] @@ -489,7 +489,7 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.returnImage, img); - this.localDecoder.Verify(x => x.Decode(this.DataStream, null, Configuration.Default)); + this.localDecoder.Verify(x => x.Decode(Configuration.Default, this.DataStream, null)); } [Fact] @@ -498,7 +498,7 @@ namespace ImageSharp.Tests Image img = Image.Load(this.FilePath, this.localDecoder.Object, this.decoderOptions); Assert.NotNull(img); - this.localDecoder.Verify(x => x.Decode(this.DataStream, this.decoderOptions, Configuration.Default)); + this.localDecoder.Verify(x => x.Decode(Configuration.Default, this.DataStream, this.decoderOptions)); } [Fact] @@ -508,7 +508,7 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.returnImage, img); - this.localDecoder.Verify(x => x.Decode(this.DataStream, this.decoderOptions, Configuration.Default)); + this.localDecoder.Verify(x => x.Decode(Configuration.Default, this.DataStream, this.decoderOptions)); } public void Dispose() diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index 5894151dca..084ad59938 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -149,7 +149,7 @@ namespace ImageSharp.Tests } - public Image Decode(Stream stream, IDecoderOptions options, Configuration config) where TColor : struct, IPixel + public Image Decode(Configuration config, Stream stream, IDecoderOptions options) where TColor : struct, IPixel { var ms = new MemoryStream(); From 4ec0d1ee3c23f17e2a9a484f0b04fc7e80316af8 Mon Sep 17 00:00:00 2001 From: Johannes Bildstein Date: Sun, 26 Mar 2017 17:14:47 +0200 Subject: [PATCH 68/83] improve performance of EndianBitConverter and small formatting fixes --- src/ImageSharp/IO/BigEndianBitConverter.cs | 84 ++- src/ImageSharp/IO/EndianBinaryReader.cs | 4 +- src/ImageSharp/IO/EndianBinaryWriter.cs | 52 +- .../IO/EndianBitConverter.Conversion.cs | 63 ++ .../IO/EndianBitConverter.CopyBytes.cs | 145 ++++ .../IO/EndianBitConverter.GetBytes.cs | 139 ++++ .../IO/EndianBitConverter.ToType.cs | 141 ++++ src/ImageSharp/IO/EndianBitConverter.cs | 682 +----------------- src/ImageSharp/IO/LittleEndianBitConverter.cs | 80 +- .../BigEndianBitConverter.CopyBytesTests.cs | 230 ++++++ ...=> BigEndianBitConverter.GetBytesTests.cs} | 110 +-- .../IO/BigEndianBitConverter.ToTypeTests.cs | 158 ++++ ...LittleEndianBitConverter.CopyBytesTests.cs | 230 ++++++ ...LittleEndianBitConverter.GetBytesTests.cs} | 110 +-- .../LittleEndianBitConverter.ToTypeTests.cs | 157 ++++ 15 files changed, 1564 insertions(+), 821 deletions(-) create mode 100644 src/ImageSharp/IO/EndianBitConverter.Conversion.cs create mode 100644 src/ImageSharp/IO/EndianBitConverter.CopyBytes.cs create mode 100644 src/ImageSharp/IO/EndianBitConverter.GetBytes.cs create mode 100644 src/ImageSharp/IO/EndianBitConverter.ToType.cs create mode 100644 tests/ImageSharp.Tests/IO/BigEndianBitConverter.CopyBytesTests.cs rename tests/ImageSharp.Tests/IO/{BigEndianBitConverterTests.cs => BigEndianBitConverter.GetBytesTests.cs} (54%) create mode 100644 tests/ImageSharp.Tests/IO/BigEndianBitConverter.ToTypeTests.cs create mode 100644 tests/ImageSharp.Tests/IO/LittleEndianBitConverter.CopyBytesTests.cs rename tests/ImageSharp.Tests/IO/{LittleEndianBitConverterTests.cs => LittleEndianBitConverter.GetBytesTests.cs} (53%) create mode 100644 tests/ImageSharp.Tests/IO/LittleEndianBitConverter.ToTypeTests.cs diff --git a/src/ImageSharp/IO/BigEndianBitConverter.cs b/src/ImageSharp/IO/BigEndianBitConverter.cs index 0841027282..cf9fc1a875 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[0] << 8) | value[1]); + } + + /// + public override int ToInt32(byte[] value, int startIndex) + { + CheckByteArgument(value, startIndex, 4); + + return (value[0] << 24) | (value[1] << 16) | (value[2] << 8) | value[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[0] << 24) | (value[1] << 16) | (value[2] << 8) | value[3]; + long p2 = (value[4] << 24) | (value[5] << 16) | (value[6] << 8) | value[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 3b2028afdd..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) { - EndianBitConverter 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/LittleEndianBitConverter.cs b/src/ImageSharp/IO/LittleEndianBitConverter.cs index 63ebe18a33..b6c821e3d0 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[1] << 8) | value[0]); + } - return ret; + /// + public unsafe override int ToInt32(byte[] value, int startIndex) + { + CheckByteArgument(value, startIndex, 4); + return (value[3] << 24) | (value[2] << 16) | (value[1] << 8) | value[0]; + } + + /// + public unsafe override long ToInt64(byte[] value, int startIndex) + { + CheckByteArgument(value, startIndex, 8); + long p1 = (value[7] << 24) | (value[6] << 16) | (value[5] << 8) | value[4]; + long p2 = (value[3] << 24) | (value[2] << 16) | (value[1] << 8) | value[0]; + return p2 | (p1 << 32); } } } \ 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..143ae00e95 --- /dev/null +++ b/tests/ImageSharp.Tests/IO/BigEndianBitConverter.ToTypeTests.cs @@ -0,0 +1,158 @@ +// +// 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)); + } + + /// + /// 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)); + } + + /// + /// 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)); + } + + /// + /// 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)); + } + + /// + /// 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)); + } + + /// + /// 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)); + } + + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void GetBytesULong() + { + 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)); + } + } +} \ 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..be3ae3f47c --- /dev/null +++ b/tests/ImageSharp.Tests/IO/LittleEndianBitConverter.ToTypeTests.cs @@ -0,0 +1,157 @@ +// +// 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)); + } + + /// + /// 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)); + } + + /// + /// 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)); + } + + /// + /// 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)); + } + + /// + /// 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)); + } + + /// + /// 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)); + } + + /// + /// 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)); + } + } +} \ No newline at end of file From c5c33a730e5b70dc78184e0328812ac8cf5b1e9b Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 26 Mar 2017 17:55:17 +0100 Subject: [PATCH 69/83] fix broken tests --- tests/ImageSharp.Tests/Image/ImageLoadTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs index a7f57ca1c3..ddb9414cc4 100644 --- a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs @@ -46,7 +46,7 @@ namespace ImageSharp.Tests this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((s, o, c) => { + .Callback((c, s, o) => { using (var ms = new MemoryStream()) { s.CopyTo(ms); From 72e88f3a33c8669b8a14b950ed7370e272910cd6 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 26 Mar 2017 22:26:03 +0100 Subject: [PATCH 70/83] allow test cases to be serialized to they show in VS --- .../ImageProviders/BlankProvider.cs | 25 ++++++++++- .../ImageProviders/FileProvider.cs | 20 ++++++++- .../ImageProviders/SolidProvider.cs | 38 +++++++++++++--- .../ImageProviders/TestImageProvider.cs | 44 +++++++++++++++---- .../ImageProviders/TestPatternProvider.cs | 21 ++++----- .../TestUtilities/ImagingTestCaseUtility.cs | 15 ++++--- 6 files changed, 131 insertions(+), 32 deletions(-) diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs index ad4d2cc986..61067e53b6 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs @@ -6,6 +6,7 @@ namespace ImageSharp.Tests { using System; + using Xunit.Abstractions; public abstract partial class TestImageProvider where TColor : struct, IPixel @@ -17,14 +18,34 @@ namespace ImageSharp.Tests 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 7975f9b7e3..a641cfa4d1 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,6 +34,10 @@ namespace ImageSharp.Tests this.filePath = filePath; } + public FileProvider() + { + } + public override string SourceFileOrDescription => this.filePath; public override Image GetImage() @@ -49,6 +54,19 @@ namespace ImageSharp.Tests return this.Factory.CreateImage(cachedImage); } + + public void Deserialize(IXunitSerializationInfo info) + { + this.filePath = info.GetValue("path"); + + base.Deserialize(info); // must be called last + } + + public 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 1593014ae5..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,6 +34,15 @@ 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})"; @@ -44,6 +54,24 @@ namespace ImageSharp.Tests 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 4ec0fb5074..5a73bbcc95 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 : IXunitSerializable where TColor : struct, IPixel { public PixelTypes PixelType { get; private set; } = typeof(TColor).GetPixelType(); @@ -25,7 +26,9 @@ 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 TestPattern( int width, int height, @@ -72,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) { @@ -85,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 index 89f46a947e..39ce614956 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs @@ -8,6 +8,7 @@ namespace ImageSharp.Tests using System; using System.Collections.Generic; using System.Numerics; + using Xunit.Abstractions; public abstract partial class TestImageProvider where TColor : struct, IPixel @@ -17,21 +18,21 @@ namespace ImageSharp.Tests /// A test image provider that produces test patterns. /// /// - private class TestPatternProvider : TestImageProvider + private class TestPatternProvider : BlankProvider { static Dictionary> testImages = new Dictionary>(); public TestPatternProvider(int width, int height) + : base(width, height) { - this.Width = width; - this.Height = height; } - public override string SourceFileOrDescription => $"TestPattern{this.Width}x{this.Height}"; - - protected int Height { get; } + public TestPatternProvider() + : base() + { + } - protected int Width { get; } + public override string SourceFileOrDescription => $"TestPattern{this.Width}x{this.Height}"; public override Image GetImage() { @@ -43,9 +44,9 @@ namespace ImageSharp.Tests DrawTestPattern(image); testImages.Add(this.SourceFileOrDescription, image); } - - return new Image(testImages[this.SourceFileOrDescription]); } + + return new Image(testImages[this.SourceFileOrDescription]); } /// @@ -132,7 +133,7 @@ namespace ImageSharp.Tests 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). /// diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs index 1c960e0e86..9fd33d90b6 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -62,7 +62,7 @@ namespace ImageSharp.Tests if (string.IsNullOrWhiteSpace(extension)) { - extension = ".bmp"; + extension = ".bmp"; } if (extension[0] != '.') @@ -81,9 +81,9 @@ namespace ImageSharp.Tests tag = tag ?? string.Empty; if (tag != string.Empty) { - tag= '_' + tag; + tag = '_' + tag; } - + return $"{this.GetTestOutputDir()}/{this.TestName}{pixName}{fn}{tag}{extension}"; } @@ -111,10 +111,15 @@ namespace ImageSharp.Tests } } + 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) From 2e53515b98de61ecbf954d555756c9ede9de49e4 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 26 Mar 2017 22:27:26 +0100 Subject: [PATCH 71/83] Speed up greyscale tests by using test pattern. --- .../Processors/Filters/GrayscaleTest.cs | 34 ++++++++++--------- .../TestUtilities/TestImageExtensions.cs | 20 +++++++++++ 2 files changed, 38 insertions(+), 16 deletions(-) create mode 100644 tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs 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/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); + } + } + } +} From 5763412df59fb19e5de1816f08758288bd08bc95 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 26 Mar 2017 22:46:17 +0100 Subject: [PATCH 72/83] fix broken tests --- .../TestUtilities/ImageProviders/BlankProvider.cs | 2 +- .../TestUtilities/ImageProviders/FileProvider.cs | 4 ++-- .../TestUtilities/ImageProviders/TestImageProvider.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs index 61067e53b6..6dc0d89c52 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs @@ -11,7 +11,7 @@ namespace ImageSharp.Tests public abstract partial class TestImageProvider where TColor : struct, IPixel { - private class BlankProvider : TestImageProvider + private class BlankProvider : TestImageProvider, IXunitSerializable { public BlankProvider(int width, int height) { diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index a641cfa4d1..bc18209f32 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -55,14 +55,14 @@ namespace ImageSharp.Tests return this.Factory.CreateImage(cachedImage); } - public void Deserialize(IXunitSerializationInfo info) + public override void Deserialize(IXunitSerializationInfo info) { this.filePath = info.GetValue("path"); base.Deserialize(info); // must be called last } - public void Serialize(IXunitSerializationInfo info) + public override void Serialize(IXunitSerializationInfo info) { base.Serialize(info); info.AddValue("path", this.filePath); diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs index 5a73bbcc95..26192ba1e1 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Tests /// Provides instances for parametric unit tests. /// /// The pixel format of the image - public abstract partial class TestImageProvider : IXunitSerializable + public abstract partial class TestImageProvider where TColor : struct, IPixel { public PixelTypes PixelType { get; private set; } = typeof(TColor).GetPixelType(); From fac9b469ef948bc0a42e8afa90da42f70c1e4db2 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Mon, 27 Mar 2017 07:42:06 +0100 Subject: [PATCH 73/83] fix Image.Load api changes --- tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs index 9249030398..882f903d68 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs @@ -29,7 +29,7 @@ namespace ImageSharp.Tests.Formats.Png image.Save(ms, new PngEncoder()); ms.Position = 0; - using (Image img2 = new Image(ms, new Configuration(new PngFormat()))) + using (Image img2 = Image.Load(ms, new PngDecoder())) { // img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder()); ImageComparer.CheckSimilarity(image, img2); @@ -50,7 +50,7 @@ namespace ImageSharp.Tests.Formats.Png image.MetaData.Quality = 256; image.Save(ms, new PngEncoder()); ms.Position = 0; - using (Image img2 = new Image(ms, new Configuration(new PngFormat()))) + using (Image img2 = Image.Load(ms, new PngDecoder())) { // img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder()); ImageComparer.CheckSimilarity(image, img2); @@ -73,7 +73,7 @@ namespace ImageSharp.Tests.Formats.Png image.Save(ms, new PngEncoder()); ms.Position = 0; - using (Image img2 = new Image(ms, new Configuration(new PngFormat()))) + using (Image img2 = Image.Load(ms, new PngDecoder())) { ImageComparer.CheckSimilarity(image, img2); } From 5b02e9a8d4fa3a576c3009163fba970c8c523242 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Mon, 27 Mar 2017 07:42:16 +0100 Subject: [PATCH 74/83] remove ignored tests --- .../Formats/Png/PngEncoderTests.cs | 37 ------------------- 1 file changed, 37 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 31b14601af..51cb0cdc00 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -15,43 +15,6 @@ namespace ImageSharp.Tests public class PngEncoderTests : FileTestBase { - [Fact(Skip ="Slow intergration test")] - public void ImageCanSaveIndexedPng() - { - string path = CreateOutputDirectory("Png", "Indexed"); - - foreach (TestFile file in Files) - { - using (Image image = file.CreateImage()) - { - using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.png")) - { - image.MetaData.Quality = 256; - image.Save(output, new PngFormat()); - } - } - } - } - - [Fact(Skip = "Slow intergration test")] - 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); - } - } - }); - } - [Theory] [WithBlankImages(1, 1, PixelTypes.All)] public void WritesFileMarker(TestImageProvider provider) From f1517d2126d00579fe93f89760155b7f9a60bf39 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 28 Mar 2017 00:59:14 +0200 Subject: [PATCH 75/83] fixing #146 --- src/ImageSharp/Colors/Color.BulkOperations.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Colors/Color.BulkOperations.cs b/src/ImageSharp/Colors/Color.BulkOperations.cs index 039fafced6..7d3b12ea0b 100644 --- a/src/ImageSharp/Colors/Color.BulkOperations.cs +++ b/src/ImageSharp/Colors/Color.BulkOperations.cs @@ -42,6 +42,12 @@ namespace ImageSharp 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( @@ -92,7 +98,7 @@ namespace ImageSharp /// 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); From 4231a109953009ea162df2e5e9c9440b61b58573 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 28 Mar 2017 02:26:18 +0200 Subject: [PATCH 76/83] PackFromVector4ReferenceVsPointer benchmark --- .../Bulk/PackFromVector4ReferenceVsPointer.cs | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4ReferenceVsPointer.cs 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 From 5899ceee8c771ea67e9c024e0a76c3c682a64efd Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 28 Mar 2017 14:40:51 +1100 Subject: [PATCH 77/83] Update to use MathsF --- .../Brushes/ImageBrush{TColor}.cs | 2 +- .../Paths/RectangleExtensions.cs | 8 +- src/ImageSharp.Drawing/Pens/Pen{TColor}.cs | 2 +- .../Processors/FillRegionProcessor.cs | 4 +- src/ImageSharp/Colors/ColorspaceTransforms.cs | 8 +- src/ImageSharp/Colors/PackedPixel/Bgr565.cs | 28 ++-- .../Colors/PackedPixel/NormalizedShort2.cs | 20 +-- .../Colors/PackedPixel/NormalizedShort4.cs | 36 ++--- .../Colors/PackedPixel/Rgba1010102.cs | 28 ++-- src/ImageSharp/Colors/PackedPixel/Rgba64.cs | 28 ++-- src/ImageSharp/Colors/PackedPixel/Short2.cs | 16 +- src/ImageSharp/Colors/PackedPixel/Short4.cs | 28 ++-- src/ImageSharp/Colors/Spaces/CieLab.cs | 8 +- src/ImageSharp/Colors/Spaces/Cmyk.cs | 4 +- src/ImageSharp/Colors/Spaces/Hsl.cs | 12 +- src/ImageSharp/Colors/Spaces/Hsv.cs | 12 +- .../Colors/Vector4BlendTransforms.cs | 11 +- .../Common/Extensions/Vector4Extensions.cs | 4 +- src/ImageSharp/Common/Helpers/ImageMaths.cs | 22 +-- src/ImageSharp/Common/Helpers/MathF.cs | 144 ++++++++++++++++++ .../Convolution/Convolution2DProcessor.cs | 6 +- .../Effects/BackgroundColorProcessor.cs | 2 +- .../Effects/OilPaintingProcessor.cs | 6 +- .../Processors/Overlays/GlowProcessor.cs | 2 +- .../Processors/Overlays/VignetteProcessor.cs | 6 +- .../Transforms/ResamplingWeightedProcessor.cs | 2 +- .../Processors/Transforms/RotateProcessor.cs | 10 +- .../Transforms/Options/ResizeHelper.cs | 36 ++--- src/ImageSharp/Quantizers/Octree/Quantizer.cs | 2 +- 29 files changed, 320 insertions(+), 177 deletions(-) create mode 100644 src/ImageSharp/Common/Helpers/MathF.cs diff --git a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs index 718705b39a..ace929bd6a 100644 --- a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs @@ -80,7 +80,7 @@ namespace ImageSharp.Drawing.Brushes 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)); } /// 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/Pens/Pen{TColor}.cs b/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs index ffcc5ee757..e3716124e3 100644 --- a/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs +++ b/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs @@ -241,7 +241,7 @@ namespace ImageSharp.Drawing.Pens float distanceFromStart = length - start; float distanceFromEnd = end - length; - float closestEdge = Math.Min(distanceFromStart, distanceFromEnd); + float closestEdge = MathF.Min(distanceFromStart, distanceFromEnd); float distanceAcross = closestEdge; diff --git a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs index 9a616d408b..80a3e67932 100644 --- a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs @@ -128,8 +128,8 @@ namespace ImageSharp.Drawing.Processors // points will be paired up float scanStart = buffer[point] - minX; float scanEnd = buffer[point + 1] - minX; - int startX = (int)Math.Floor(scanStart); - int endX = (int)Math.Floor(scanEnd); + int startX = (int)MathF.Floor(scanStart); + int endX = (int)MathF.Floor(scanEnd); if (startX >= 0 && startX < scanline.Length) { 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/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/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/Processing/Processors/Convolution/Convolution2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs index 77608e02be..fa06a863ec 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs @@ -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/Effects/BackgroundColorProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs index 3568afe418..d928eb1a47 100644 --- a/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs @@ -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/OilPaintingProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs index c5302bad03..957955c6c4 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs @@ -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/Overlays/GlowProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs index bb54c1f98b..6eeb7398aa 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs @@ -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 6eda3e42a0..40d6d94ac9 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs @@ -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/ResamplingWeightedProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs index 255124a754..1374e58156 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs @@ -86,7 +86,7 @@ namespace ImageSharp.Processing.Processors } IResampler sampler = this.Sampler; - float radius = (float)Math.Ceiling(scale * sampler.Radius); + float radius = MathF.Ceiling(scale * sampler.Radius); WeightsBuffer result = new WeightsBuffer(sourceSize, destinationSize); for (int i = 0; i < destinationSize; i++) diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs index eb206380c1..16e0b6635f 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs @@ -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/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/Quantizers/Octree/Quantizer.cs b/src/ImageSharp/Quantizers/Octree/Quantizer.cs index 0e6540d426..ccf8da3df6 100644 --- a/src/ImageSharp/Quantizers/Octree/Quantizer.cs +++ b/src/ImageSharp/Quantizers/Octree/Quantizer.cs @@ -171,7 +171,7 @@ 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; } From 916d156a01e60c2fc2782cd870d5e4f8af414fbc Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 28 Mar 2017 17:34:28 +1100 Subject: [PATCH 78/83] Add MathF equality tests --- tests/ImageSharp.Tests/Helpers/MathFTests.cs | 69 ++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 tests/ImageSharp.Tests/Helpers/MathFTests.cs 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 From 853189aa782027ea8e46cf9735caabf2d519031b Mon Sep 17 00:00:00 2001 From: Johannes Bildstein Date: Tue, 28 Mar 2017 17:11:01 +0200 Subject: [PATCH 79/83] fix error where startIndex was ignored in To methods --- src/ImageSharp/IO/BigEndianBitConverter.cs | 8 +-- src/ImageSharp/IO/LittleEndianBitConverter.cs | 8 +-- .../IO/BigEndianBitConverter.ToTypeTests.cs | 58 ++++++++++++++++++- .../LittleEndianBitConverter.ToTypeTests.cs | 55 ++++++++++++++++++ 4 files changed, 120 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/IO/BigEndianBitConverter.cs b/src/ImageSharp/IO/BigEndianBitConverter.cs index cf9fc1a875..debe1706c9 100644 --- a/src/ImageSharp/IO/BigEndianBitConverter.cs +++ b/src/ImageSharp/IO/BigEndianBitConverter.cs @@ -62,7 +62,7 @@ namespace ImageSharp.IO { CheckByteArgument(value, startIndex, 2); - return (short)((value[0] << 8) | value[1]); + return (short)((value[startIndex] << 8) | value[startIndex + 1]); } /// @@ -70,7 +70,7 @@ namespace ImageSharp.IO { CheckByteArgument(value, startIndex, 4); - return (value[0] << 24) | (value[1] << 16) | (value[2] << 8) | value[3]; + return (value[startIndex] << 24) | (value[startIndex + 1] << 16) | (value[startIndex + 2] << 8) | value[startIndex + 3]; } /// @@ -78,8 +78,8 @@ namespace ImageSharp.IO { CheckByteArgument(value, startIndex, 8); - long p1 = (value[0] << 24) | (value[1] << 16) | (value[2] << 8) | value[3]; - long p2 = (value[4] << 24) | (value[5] << 16) | (value[6] << 8) | value[7]; + 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); } } diff --git a/src/ImageSharp/IO/LittleEndianBitConverter.cs b/src/ImageSharp/IO/LittleEndianBitConverter.cs index b6c821e3d0..81ed8d55c2 100644 --- a/src/ImageSharp/IO/LittleEndianBitConverter.cs +++ b/src/ImageSharp/IO/LittleEndianBitConverter.cs @@ -61,22 +61,22 @@ namespace ImageSharp.IO public unsafe override short ToInt16(byte[] value, int startIndex) { CheckByteArgument(value, startIndex, 2); - return (short)((value[1] << 8) | value[0]); + return (short)((value[startIndex + 1] << 8) | value[startIndex]); } /// public unsafe override int ToInt32(byte[] value, int startIndex) { CheckByteArgument(value, startIndex, 4); - return (value[3] << 24) | (value[2] << 16) | (value[1] << 8) | value[0]; + 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[7] << 24) | (value[6] << 16) | (value[5] << 8) | value[4]; - long p2 = (value[3] << 24) | (value[2] << 16) | (value[1] << 8) | value[0]; + 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); } } diff --git a/tests/ImageSharp.Tests/IO/BigEndianBitConverter.ToTypeTests.cs b/tests/ImageSharp.Tests/IO/BigEndianBitConverter.ToTypeTests.cs index 143ae00e95..50a86d3cc0 100644 --- a/tests/ImageSharp.Tests/IO/BigEndianBitConverter.ToTypeTests.cs +++ b/tests/ImageSharp.Tests/IO/BigEndianBitConverter.ToTypeTests.cs @@ -59,6 +59,10 @@ namespace ImageSharp.Tests.IO 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)); } /// @@ -72,6 +76,12 @@ namespace ImageSharp.Tests.IO 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)); } /// @@ -85,6 +95,12 @@ namespace ImageSharp.Tests.IO 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)); } /// @@ -100,6 +116,14 @@ namespace ImageSharp.Tests.IO 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)); } /// @@ -115,6 +139,14 @@ namespace ImageSharp.Tests.IO 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)); } /// @@ -134,13 +166,25 @@ namespace ImageSharp.Tests.IO 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 GetBytesULong() + 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)); @@ -153,6 +197,18 @@ namespace ImageSharp.Tests.IO 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.ToTypeTests.cs b/tests/ImageSharp.Tests/IO/LittleEndianBitConverter.ToTypeTests.cs index be3ae3f47c..fa8b2a1a25 100644 --- a/tests/ImageSharp.Tests/IO/LittleEndianBitConverter.ToTypeTests.cs +++ b/tests/ImageSharp.Tests/IO/LittleEndianBitConverter.ToTypeTests.cs @@ -58,6 +58,9 @@ namespace ImageSharp.Tests.IO { 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)); } /// @@ -71,6 +74,12 @@ namespace ImageSharp.Tests.IO 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)); } /// @@ -84,6 +93,12 @@ namespace ImageSharp.Tests.IO 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)); } /// @@ -99,6 +114,14 @@ namespace ImageSharp.Tests.IO 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)); } /// @@ -114,6 +137,14 @@ namespace ImageSharp.Tests.IO 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)); } /// @@ -133,6 +164,18 @@ namespace ImageSharp.Tests.IO 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)); } /// @@ -152,6 +195,18 @@ namespace ImageSharp.Tests.IO 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 From c3d762fdef6bd7333a5df866c0b39b38892937e6 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 2 Apr 2017 00:14:20 +1100 Subject: [PATCH 80/83] Update all icons [skip ci] --- build/icons/imagesharp-logo-128.png | Bin 6569 -> 8113 bytes build/icons/imagesharp-logo-256.png | Bin 13949 -> 16613 bytes build/icons/imagesharp-logo-32.png | Bin 1439 -> 1904 bytes build/icons/imagesharp-logo-512.png | Bin 31256 -> 35069 bytes build/icons/imagesharp-logo-64.png | Bin 3132 -> 3962 bytes build/icons/imagesharp-logo-heading.png | Bin 10474 -> 9450 bytes build/icons/imagesharp-logo.svg | 2 +- 7 files changed, 1 insertion(+), 1 deletion(-) diff --git a/build/icons/imagesharp-logo-128.png b/build/icons/imagesharp-logo-128.png index 6d05c222cd8e42e2990d05fb33b745a4e1b208c2..3fd4963dd3702c1f480a983667165c785657f9ae 100644 GIT binary patch literal 8113 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 LNkvXXu0mjf!>Bex literal 6569 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 literal 13949 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 aK>I&7DzXTiFlEpH0000f9B1j4 zf3@Xi#r(t0ATeMeNrVCghXHpK2TiutoQ2jhR!uABOJ#$Z+J6i!%N4{1I}pTtnQ8vf z3$Q|ASd2-@+i}40dceItXS&=zeZLRSJ>KEZ<0SZQpS$Pf_xpT4zt`vY4GA{7ySwl2 z@9)kj+?682xxnRe$@7D_sef(eM-(~#pmDm7UjEE(=TqDZ zHlg%HBhN2I_IA*Vw|Ts5Hk*)OKET7l8r0DD6xrz@XG`gYotKiRqA#vXJ-F%GhkqSe zxMg2Mh?egqs|C`)U&1=R;7Lu5b^Cw`{8j8Xv4CQ@wB) zcVKGA<9~40?1INJC9Kb24b~#feX@96UELyjU<>)27CC3mc?0`E3_gzKJh>hiU+@IO`!uKf{>_V#92 zOE#+FUT}|yxEMZjA%zWm$TyNC2@4O}hOqu*Hh3*IL+WZ4aEL|k@ zdOdW{#zU7FE2HKe9h}vLD%>q+gq)1w;b9(zb0Os7N}%!I_RCJyK-}?6Bxk3Pa4HOj z41biDm!qPh0=c=lNPqen7!s4wxu*yw1${*agq#dz9$XEfaWL_oEh@yw`5|955c{_}X=tJ$-DPgECK(u%4X_(gAx7@`Ew>GP zrA2t+YQ$;dQC3!_mZ_?$f?lV`hKNX<^nW!#a8HU?gKA*5A^48wK+)xrX*7L~=d!hk zON~diAsdyIm9n_Okb{)uG~}dhK$DMnTWqqBlc5d){REtZ4OfRyP}Zy>b=x7ttkbH< zq?RW~Rn2Qdg^R>5xTnxXBD44BSl04Y-D!oE1T z)d;tB0~a3${>~zrOYWKFBI2QUy#+(6+2U&B{el;vS3m(F2iM&@%E5`-RgRuOR#f- znw&DRH#y~`l)=~hOzCQdovur|y1M3`oX!m2^qd6h31+(HESSO3KsZd#Um&wVrsp9c a{{c&^W71AW&=&vz002ovP6b4+LSTX!x3w$) diff --git a/build/icons/imagesharp-logo-512.png b/build/icons/imagesharp-logo-512.png index 5d5fb854ead971869824ee645d57ee0aa5f912d2..fcde03ec48b1234c8bf170c80902f8b03c03f7f2 100644 GIT binary patch literal 35069 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 JPDHLkV1gxB*oy!F literal 31256 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 delta 3126 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 diff --git a/build/icons/imagesharp-logo-heading.png b/build/icons/imagesharp-logo-heading.png index b10d367bfda6135939e5ddea15ffed3be31293df..c56af1b001ab31e6bc4e4490e3241d8b4caf7497 100644 GIT binary patch literal 9450 zcma)iWn5I<7cM1T(hMab-ONZy4&4nR4bsvbg2d1zh{PZ%42YnFfHcz53?V8FA|Z`5 zcfbGp<$k;$W(MXr`<%U3JnLD{Ith9@sw9LELJSNH5_L5t0}KqzDDbxpJ{EW{v|n8V zA2?15+6ovLpVEk~ZSR5q3B1&v_+ns?cK`RnY|e1o17FhnDVzBjdOG?A+WI`h@UnIB z@DucS=Jx<9C@d%<2E~3Ff`P%|q^_i36lAqmKoCSacRO(B9~%1fow;&jwni_FCgi?i zFO7x+I}~m8qx7>YzIidq?R{fb(&t2TgRBLYnpu;%y!jalO@4y!917`Wcb;OMfp0T? z9T!Jh9gdOX$Dx>eHYgEi|Pz)XGEV^>@9lvl~aH3Cu;rv=>Hi}hZ3tk-JBY9Rvy4ql;?;s^!g$dNpg`KIR4N7Pp} zIILA7rOl!3QS7XifPT&H{1R;Ra$XfqUkR6ZJx?CSo8`R^*?9nC{`*AjA=titzX{2W z43VZEm62VzUOO=|+($|6bOepV%ax&)$oa^Hd*{$jhwYJyV~B+DbGDm7Tq_yHGLQ2p z4NZ<$Z1Ja%;V_BR%=l6L=qktMCK@^Rd(}~m>5*@6@z#@d=2e4Pa$TbDM&J zav!=S`x4cjJc?ZS&s;2DAc>(tg~zFMLw|UT7L+#Y)r{rU#djXL4gXM0=<32SNJ|hb zh?c;sJk;(qL7G&@^RGx^gWIW1%;;TNhj>lhFa2{V&pVo)VMauH8SVtvk#TJ}JbUm@ z*lT;GWc=#yA$w508C9iRO2rvS7ez!3LSk*JFZZ0NAu6GI4_I1A+hilehgZFtlne2Mu zdCv7==`W&cqIVe-2`SZ2n`)vWOH0>SBCZu4a%2}37e8|{@y#BBec5GzAiHLqkKWLWOFCl4LBhz2AQ}c`jXF9JdDU z|J~d?JNhO>`>j0aaP@R&?%&xNDG7;Xy`#2+gM)dC^_VyCd3TovDzmy$SZ#sJsc7D$L z$+36q8#W@z``O^&d-nds`!b@tm(4==$6vj2F->8GoiIlJdHw$Vj@<1bnN%<$12W7K zL}IDk(S6aU*1*DmLt^~ThciOhZ+Dr2ZX)$|&~Y1GDN)Ndkj7F$!5L-VbQs_@m1J+c zZGPXSg8yp4$`yt3J>53e)zy`HPQwPvk~@Ip`Pvaw(6Q?`HS$4YwoYuh4B|;ACnppA zpz4~Mr9PbI(k5%WrMSOoqrKh!iZ{IPPlxbM{cH`absB%`H^#5(${n!`L(lH+REHEx zs1Y2+(Ez&``q?AUXd~VN?Fv#R$t%>SA5E0tCf@xOlzUYoWr##Ui9olx8Uf=fs)BkX z6`x79_gX)&PI7#_M9%0-Y`lQIg;=YF_olT?!0oZLR_Xscx%UeZiVr6KjxaRJlF}eP zk}c2T$rnGK59!*id1J`PtgR=%at)Kai!5OXwBF=HNy!$^r4FHvbG8C8#A;uXZm#G< zS=rm~^-dCf4T@)f#s&w2uFnq(qytT=&Gb88gjW`ab&*0`4`0i@XN@T=WZ{Xi{rj^e z@maF`jzjbpfHC*iN3Qy8CTbvXSJV~-6Vu-At`=AFO5xo#*E-t^5}~8z?r0PWb>eqx z-H{2xDPTe`dU8~fq$OyMD^@t&Cb?V2fogd&#+Q8crfNpAla6@7*j|l<+A@_%dUI<_ zjVrl(Xz0(u%2#&^2Ob0XSs&PqY@I*_tCD=2yg@txMY2f9zrWh=bC(&*aDYn`T3wSL zPmv4OO9$-YlaM_B^`$XC|LGe+OUE%}@Ws)y(ci_QVq%}1zt=qrzdSiSIFP>>E07+~ z6~kk(yH71DZ*IN-tRXUYelo55qtPucL|$GVBkw*lGczTneczxGwoaj}jD3G{jb-a2 z=ZPZVq#V2t7A;4Af3<#2_N6w8LKFwHva-77%Uy1jUL5}}lnx{{j=N%HLg

xBp%h}$mj1~vG<{UudiQ$>tcau${} zEZsucUkxsq00j7I+m2R~ggOp-h)^HQKH25)7@_nqMWc$ds;DR^D1@;z-w57JX$n>9 zc5rWi0GAFva_|e}t-806CaJ8Xq~zswknh=%kW#|z->PAJP6@}?j3Z?VIo{Ncw!{mk zjV8Q`m71hGZE=87KYhOaQ^Y5qQfPoE4p|aLzs7;fq_Ulo6nSV)c!}Z zwoTH!N4?|d+3xpJ@sD@sYh;JLm{970?`=IJ_EvDc4l?McYBvwbC>?uXmHvDdNl8hK%vt>Y zrODlz9pgW+1lUgV`Wr#O@c>K2h`;#h#Pq~sd+6HgYEhc0b@*%987voeQCPbcjk@5G zY_cnF0YkG=Y;0_?+vFwI-NkqWiW}R!IpXdG$A>{H<4(SY=RZtlCWJb#kIsI0PGeEN z(|B#(9s+yi=5XS_15V?AaB>o}Kccw=(iX;-PMHo_Pjh!1%~MVl!T6AwnTm;7jfipu z6zsov{8)iYq;#4|)yoIUigxh@qQ2PO%noliDGG=dg`?;hn;!`$c7HMSy0ZPSFG^j}Xd-`H!DV4kX#j9H{%BSgwm zjM$^ASU1r214|)00$T2`4QH#2`5H{x)4oO#g5zm63Ol>vGtsFU80NLO;-f+nRZ2v* z%rWjt=aeT8XFoix_#I*eUn+Q7n$uUJXT3Q=e5CqFLTjBtp!tpDLet!NZo}Ibw~2_L zPP!yMxaw352V?z83QaKsmzK z*MlJ)^^-xJF@zyfsI9H7H^y2SO%C}SxNjwLzssioK`)!J2pRKZ06njQoQ6k7_l95d zY0%$8xA^avu>0caeRhAsVV5J#_3+^z61sabB$!CiLCcFiHjI4s_cK}d7dl_ugoteP z(pQ={x;DDbNCoW1XQs-mdQ@{a6$)3DC2B2Gg0HPu3gAACVlb}4CN{^2I3ZQ9lb7e~xjv$_FC5Za=E&e_`D1Prq3ACgp?lhbq zvj6@2_a{D%Gqb3k)*367*gyQc>to$&9qQaJlVyzns9jgl`;}p>hjhF~DB;jE_s^B* zg$&{z#0-zLJolAIiql@4uf(m!KcB7s{%5-4do~8+7iO6dFUk8cN$evJmF5p;-kUNM zDl8jJ0uDYns3!JL6eQez{9kL%?>{I%L}?uVGH1-q6?00I?p=VW%^ugVo+apV-cNk`Z2NwESjWR`UmP z&3Y*;a#qJ>rK)=ijhZ||-EQ0)>f1%__T;QrTvdk`EYtlgZ;N{yN{OJQ;Z6}^3WT1iOhPPEA({Vn z2=U4V9Xt>WH{#|RlotM2i6Y>}4o=J=FQz!m=Gq>h8O2w_T%$4dtcu5!Mx6;q7AV&} zC|4XbN%u`?p_}cym}%t`a>b8Rymlpa?B=HUtwFaaOqy-b;x5e7^;4s*C6%Q3V-Uq@ z*MSP**C#p3x^NIeiHV7;vx*^6Ntkm+kzcXg;>VvBwMkfRba56DA4bKLe&7)0cZ9~+ z*9GjEJ495qe)4CO-_UgjizWV$XP0wNQl=jnwji}~MV0Htk05oB5p#8l07Y-|5?dMF z%lMTbfxs~#+~_GfLw%Z|B`@YtYN~@T*7VmaFo8uSp0b18wo<*$jv$?=?f2 zB)lRC>9Jc8vM-s_JmS7VjC;u`D0&{z&B=_`E@sHu^!KL7+zX4xA+*d+6!n5AYI-Tq zSi}2%;(W)X9v=E+KQNrd1K-dpV(IViHz2^>#fPToClWQ5(pR;s?~Qb9rpvW!U1jPbGi!oP7d;?7VK0eo$*z9{>cL_T{(TdHfr$QDzmA8r zPVOc6jX+y`UDldchS${80CHQRNW2w0Skot+s-vTm^UiL^XbeX-SKRaMW;W$%U*8=Ku|9!xWvKNm2MNK9;Dm$_?K+I zTXn%;G5u@4qlR9Gt9>D-+xn6SK;=um>ahwuVA_>E^Sisfk>~_H%-+uyU)2;yEVZ{_ zuZJE0qI#!spXU>3@&J~(&aEwH-m-h!lW6kKNsr>6R1gxs3!eI+tzY zfAW&IhH&%rl)4tfx6dWNtwY4Q#`R`lq+>Cb(3o#%z#uBgv0de-ST~7CAW(^j+cw8S zm06oggs^0uh&}FQO+^J4Cn;Tf9?Z)Nve4vNxY@o4yq(ulvbsI@PLq~*xXT6qI4Uv{ z!@M6uj;)(u3TuGg(2~~)tKGUSqc=*~+uOVI=IjTc-=HhgdH*M}nv$7npsE}(kpmhi zg9Kz(axB{|>ga9!9F4$OAPM%Z96{TOQ5^;OMMLnFL4C#6326!`IgZ*6WyhwyF zFSO|j)b^xIZR*`+RhDq2|6IM&Y}+;|%HZYgEmF%$jiRJ|POL2Q&aS)HX=C`cg<@F+ zYoo7^SsAOl8zB4ulygcTE#@ET=E$WEz_CVHxcO($dVi+giM!7~SQODys0Bq1`?Vv= zsFI@2p0~H?L$T=1i;$KpbH3GrG!v8RALtUj^^&QM@K7CjSDQaY3K4gjO1$Eo$f1OO z3$ydybgch5GgnjA4gZdXl5)C2m&K!;J<6?P)0Z2Gn60tYB=U*M#d!kWLhybKhch7#= zvXo`Aw8xoAvn!Xo1QK$v+zp!4zFM8h=xHN2JC4h(H|MYb3TOx z2LtndG=dX;9I1X%e{%aPHCx~*0EhfU$4pg7PuJq-=lPrU_fTyrDG)WfPlS%HUlAlb zOP!rTJ}GWnGrNJqs2Ea5XhY~f4-i#U3>P@B9lZeWvoXMxXfdOLNvEv&mD2Bb4BX`_ z-rn9KL(tAft-pLE zdx~b|OZD~n#J#vDo0geM*KvF8NikD9lT)8YA^Wzw-BlNXxa|1n>B{o;aN7gv-5c-Y zss2u&EZ!CWjuHalT_h$XAW&CVU-`oGQ5g;@vbb*(g{hplxcL9_w)ES;KwNgN{8e;R zl%VAo9Cdn}(|BulddvX_fU|j>^m?FF*$<@Zx40jv!GYRBPRUqCJ(00}#_`_ia~vHhyT={HS7P{^-MQR}5JeOIM_-4Actk}Fc)Fq*KMEPeznmry`QR85 z>pPJ8kOrN+YgyljL_APE(Vxt3MkS7WL2#n)x?W;Cc9?F?18r$j+63k{tHh}oTQ@{R zvZ9wpClb}VW?1Ji1lrJ~alw@?kNJ-ZVH~0n&={El3Gb5gm@KSW^Ew9t>QRCkv%DkG ziej?OzrRQ9lqD`+^bn{KD9i98!^s6K!sUE&d!*C|fTd%XWs!mN@$M5*!%s+4UG&it zjc#+jEK!pTQ-hg(S~y;i9U)bIHixK5}9y4Nq%s2^u=ohtBEysXRcPtyepFwnr?M+KXQm0oW1uo&Xg`>|ydUesVxH>ZKW|vran)heOFJ)?08oHD~ z|JCwdsw<3;+Eq5Q$Aw!&MF04y8MNH6LPy+lvCM~4*r;5$P*8`FN`j<#<42PxZRq2= zXrTA_1jds@6)0oHZ_c3fu}wX-$Y60Kk(&X#^ZKm;&kqm%CYn601>X$=_e30U&0(xd zgRWp>vu&OzzR{{Zq-5z>8@_dyw?A5xxpSjKv`~HlqaRd;FYVrD`X&mj%tt)iPw_n|)A;YuctCx~B%7(^P)}PzkZ><)FpV9xQ z5V`*B@vG|Jsc;4BIuYH->L?M$lVy!D*Fc$MP7zE{*aSHI@`rr5N$nabovmWsCYwgzr2tu$PhJ09ax3gz# z(Gt2UoqH=AehCzq(y-tEDdo7$x~9CglAC&IzT{ET53{3Yl1dQ550)bC!p+k&0sGkd z(ImvnyB%?NwUotUIQ&LPmk0;=!>1ZW>VCh>2!PwyII=icdgMffR6InHrSE{on$Ixk zcvO5KnLaxCPj3R{LDJMMfVQz*v5MzEKBwC!(Fmm1>RW#Q2&p4A!{Ab6oDVK$WlTv= zCm*eNY2&no@#XQ0>vJHG|w0bM(qCQ`rmGyxsKlef&7|t*>5BseY+InL9$ zU3nw|16i6Ht+jgJ)JgIa*^aWDGM z;xJ+4md}$MS;JN24Na@fY&Un%KVdkW*HnGqd$*TK$(Psd9$%tonoYy2yxnFLD)JgD zbe|Uipj1*(NoFC0S?9IgUhbft9M1(nVK`nGpDg*bvS=Ya zkE8Opc-CVPk)lGS&m0x%F8XM#sg}i`0SuT$zD+9fBm)iAKHfMLu?xNt!x<456`$Q| z$nEXc%Tax#1%Dz;EYMJyvrbghh-6arvpy&>c|dme++@0UkCv9U>=JN>(oTBN>T3hx zpDq|p&Py-qpa$Z2^J!`b1PjGdE|xP~>FkOEB&K0rQMkuxH1FZx4n<}c`~jNZio5QN zTcH5Fzs59R6f&59w83=zmv@tXuio6+dg81}3%h*_Gbh89HtL2x5 z%{PaAfYE@~zTDIq0}V){yoJS2bfuy9kbujXst@%?_SDo=pv!J{GfQcQSRx`X*8zdS}+6q;NGO{>(gXmbg3k zN;V<)4P+CT;_%9*M0pc20TnR}CAqDKt@P1(l8x8N?&k7TUFvS^C+K)>*?0$vF2szs zY5^&Vy)P`LSQ`tQc)f$JoNf*DMEwTSAB{IMfqxlidOkXhGmEADC<+A`^L!*nBub~n00gqw^6;=Q;cg=q@BYtKCLa=I zwig=tq%MIPA#w(dC?pPz`x>;|^)i--7J)!0762LmsOWYX+26>XpTB;|T_3=;y0e9> zUmUHgi@h)cWc@J{JFXu=JC{IaX6D?7r>Yxt0B2bfnN9Dldw=w@v3a0Ux5~~q52hlc zNJ+|3!w%1$&)I^O8q~S?WMs4vxRiKtG-6%aEwZ?_R&ZyPkdRBD-oX_WL2j2^>nS8q zJVX{0L`O%1nGF*?ePytL68{8w&?SFnX3}91uh!$xyl#TN?fCZME3z~-st4>R3rVy> zZzX+k3n2m>+{>3=)8{&> z1w$MgNb$CBBKr!Lx*|2!Ug z^L`RJtCrO;)+D5C7XIuqt=aJjPQB#|&r7y1Hy+tl?i zxI2N8E8y?X+#T&ir|)-DDxi(*HA4Ap42RVAXb@5>!{HA$Ca32@;(WJlqlZPjC_X*B zMBg0QCWd$9yrILvRS@#?_XpF^Q4oRY=(36mxYPvU<-?P#yu6Ux>kA;&c2u2u=zp^7 zi;9l^PMg6Io6T0r8e4eV-2GiO;WX}oB@8*WhNtt;WZ-(`I+XQQ7zV`P#-Ln_-qUj* z3=crb3kJF!xB*1c#{nqsckfQ48o{;TN=h+Wv57d!T5v+_H`i(YdbuyIZy=%J>T*@T zu#A*nYaG#km(SceJdlhy#qx+}vYB+w0Uy5zM3d60h0?qci5-99@bG3z_DRz9i z9hw8CKN>(u$HKx=Nn>sYJ_4Bb)BeS_p22#2N=iz6{QO%=WVnUtZjkx0F8tMR0zZXw zZUF%S5Eins%mt?&kIoBYVq(BB928YEoo)`?|Gy`6{@;gsDSPJ;9>I4Zz% PNf_$NI!ZN)Hc|fvTioXx literal 10474 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 e=J%%uQ=TEM%|`i-dEi_kilVFK=pu 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 From 2b74dfa54934b54ffa0f8dcb74413b6ab7ed1883 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 3 Apr 2017 12:27:46 +1000 Subject: [PATCH 81/83] Fix logos [skip ci] --- build/icons/imagesharp-logo-heading.png | Bin 9450 -> 11390 bytes build/icons/imagesharp-logo.png | Bin 59646 -> 35069 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/build/icons/imagesharp-logo-heading.png b/build/icons/imagesharp-logo-heading.png index c56af1b001ab31e6bc4e4490e3241d8b4caf7497..35a945930ac8a8653cceaa74d7fe5401eda33315 100644 GIT binary patch literal 11390 zcmaiaWmsEH*d`7If)sZx?hptr!5xYhcWLoLaS!fNthhtb(iV4jNU;`oFH&6gyx;E6 z-D`h94k0I*nLKmbGtrvr3Ls2!Oauf3kdmUTHUa`-C~$3#fdu@{x7yqS9#HJ0)ua&+ z8WOP{El`2KU%4t8dLSU+bpQ8*=r|w!3V4#tQ_jHit&6Rvw}rb6f~$ptvnQ9cjVG-D z7Y`RNzX0+(Ujzj92PIi)olmAGU(j_3_17Z1Zg(egig@MJ8f23!3%yZLq$5mP$#v=r zLYcgxE0m5qKHCdq$j)GpV#OE~GcD2=gi=um>PYz}H*y3@W{!!7^0$rC$87)E`;!Xw zI{TB=veWV+YV*{!M~E4rU8f1sVoCFbIY!A8!ufTegqWpyh2%w=EdQ{7;YG+)+Eu_< z2$jX5VA(>=L$WG5;FhWX=Plz-)0%Q}a)EL_%5qDFJ|m`gO=jB>siT(5Vo{n6dhEfhNlfmuWPNZQJK!wy&`l&oH5bqfPu< zFMj;JPDUvwd#;g|mP|}oXbe`~zzK(Nmqy-8nm;g=XHIxCY*?I{rds!jb zT3GPnBB!LAP1$9@M&ri>CV1GCg;n^+k0QuROM9-rLZK!(SykW*G z1$Oc`hF;7*=Vg0SHYLWHUwN3%7F_s;j;whdWFp(mkn+D^um`y}g^otaINZX4k+N-* z?Z{z`HBwnGiBNvEPh+dY-1%3>5+sXRTT;YG$L3Ll=%%R~n!*PKxo6yEn2OMIb25>! z5-RtV;0Q2&Ug6>s-{`rQ> z@6$gYRFeWN7EsD(cvAc0(xHeOUJA!hgG`3vea%#38d75}B zO{P$Q($q_f>9gIadj~|OQZB{jXe9q@fN76WLOL@nigqBJW?Sc2w;M`NccXW(g# z1sF6<*23_ z;`ELVKxnXCTn!mxiSOQ;+s zl?Id$oAb``XpPbj`Na(r!v^l#xqWLYYSU)?ZloMm4ya8)JuHYC-DyEiW z`&9G8yX|*9-TW$bnRqLikK#!{yLgB`(dz1IV25AA&Bs3ygJ5t8!4PY+65V}--`-d( z)<_S4SwbnV?B#8@v2ps`oRiwGLs6MuJaQPQS03r@E1(YFimlSV#*UnQ=eE5FKoNz~ z1bK{~dyfNp3*UqiF}^c0PJE!zDn@V)Z-_JUSTZbIH3l;+XB?yrTmjQ88iQuW}M?`uin}oR$lX(@>eqW7vnZkAp zctK%Nqa8!yOLqgVs9}W4yeW2SgY!qaz!Z`R_Axl?Prs$CMyh_sO2al=!=j0q=P

Jy_f)!YmXb}+6n6m8|DjE!@sS0wi$` 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 literal 9450 zcma)iWn5I<7cM1T(hMab-ONZy4&4nR4bsvbg2d1zh{PZ%42YnFfHcz53?V8FA|Z`5 zcfbGp<$k;$W(MXr`<%U3JnLD{Ith9@sw9LELJSNH5_L5t0}KqzDDbxpJ{EW{v|n8V zA2?15+6ovLpVEk~ZSR5q3B1&v_+ns?cK`RnY|e1o17FhnDVzBjdOG?A+WI`h@UnIB z@DucS=Jx<9C@d%<2E~3Ff`P%|q^_i36lAqmKoCSacRO(B9~%1fow;&jwni_FCgi?i zFO7x+I}~m8qx7>YzIidq?R{fb(&t2TgRBLYnpu;%y!jalO@4y!917`Wcb;OMfp0T? z9T!Jh9gdOX$Dx>eHYgEi|Pz)XGEV^>@9lvl~aH3Cu;rv=>Hi}hZ3tk-JBY9Rvy4ql;?;s^!g$dNpg`KIR4N7Pp} zIILA7rOl!3QS7XifPT&H{1R;Ra$XfqUkR6ZJx?CSo8`R^*?9nC{`*AjA=titzX{2W z43VZEm62VzUOO=|+($|6bOepV%ax&)$oa^Hd*{$jhwYJyV~B+DbGDm7Tq_yHGLQ2p z4NZ<$Z1Ja%;V_BR%=l6L=qktMCK@^Rd(}~m>5*@6@z#@d=2e4Pa$TbDM&J zav!=S`x4cjJc?ZS&s;2DAc>(tg~zFMLw|UT7L+#Y)r{rU#djXL4gXM0=<32SNJ|hb zh?c;sJk;(qL7G&@^RGx^gWIW1%;;TNhj>lhFa2{V&pVo)VMauH8SVtvk#TJ}JbUm@ z*lT;GWc=#yA$w508C9iRO2rvS7ez!3LSk*JFZZ0NAu6GI4_I1A+hilehgZFtlne2Mu zdCv7==`W&cqIVe-2`SZ2n`)vWOH0>SBCZu4a%2}37e8|{@y#BBec5GzAiHLqkKWLWOFCl4LBhz2AQ}c`jXF9JdDU z|J~d?JNhO>`>j0aaP@R&?%&xNDG7;Xy`#2+gM)dC^_VyCd3TovDzmy$SZ#sJsc7D$L z$+36q8#W@z``O^&d-nds`!b@tm(4==$6vj2F->8GoiIlJdHw$Vj@<1bnN%<$12W7K zL}IDk(S6aU*1*DmLt^~ThciOhZ+Dr2ZX)$|&~Y1GDN)Ndkj7F$!5L-VbQs_@m1J+c zZGPXSg8yp4$`yt3J>53e)zy`HPQwPvk~@Ip`Pvaw(6Q?`HS$4YwoYuh4B|;ACnppA zpz4~Mr9PbI(k5%WrMSOoqrKh!iZ{IPPlxbM{cH`absB%`H^#5(${n!`L(lH+REHEx zs1Y2+(Ez&``q?AUXd~VN?Fv#R$t%>SA5E0tCf@xOlzUYoWr##Ui9olx8Uf=fs)BkX z6`x79_gX)&PI7#_M9%0-Y`lQIg;=YF_olT?!0oZLR_Xscx%UeZiVr6KjxaRJlF}eP zk}c2T$rnGK59!*id1J`PtgR=%at)Kai!5OXwBF=HNy!$^r4FHvbG8C8#A;uXZm#G< zS=rm~^-dCf4T@)f#s&w2uFnq(qytT=&Gb88gjW`ab&*0`4`0i@XN@T=WZ{Xi{rj^e z@maF`jzjbpfHC*iN3Qy8CTbvXSJV~-6Vu-At`=AFO5xo#*E-t^5}~8z?r0PWb>eqx z-H{2xDPTe`dU8~fq$OyMD^@t&Cb?V2fogd&#+Q8crfNpAla6@7*j|l<+A@_%dUI<_ zjVrl(Xz0(u%2#&^2Ob0XSs&PqY@I*_tCD=2yg@txMY2f9zrWh=bC(&*aDYn`T3wSL zPmv4OO9$-YlaM_B^`$XC|LGe+OUE%}@Ws)y(ci_QVq%}1zt=qrzdSiSIFP>>E07+~ z6~kk(yH71DZ*IN-tRXUYelo55qtPucL|$GVBkw*lGczTneczxGwoaj}jD3G{jb-a2 z=ZPZVq#V2t7A;4Af3<#2_N6w8LKFwHva-77%Uy1jUL5}}lnx{{j=N%HLg

xBp%h}$mj1~vG<{UudiQ$>tcau${} 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 PNf_$NI!ZN)Hc|fvTioXx diff --git a/build/icons/imagesharp-logo.png b/build/icons/imagesharp-logo.png index ed1c36c5ea23eb1bb21eb0be9d5138575029a3b4..fcde03ec48b1234c8bf170c80902f8b03c03f7f2 100644 GIT binary patch literal 35069 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 JPDHLkV1gxB*oy!F literal 59646 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 F1OOzA^^5=j From 38db4c895cf71f257606147a15ac95b8e3d0aec9 Mon Sep 17 00:00:00 2001 From: Pia Mancini Date: Mon, 3 Apr 2017 20:57:03 -0300 Subject: [PATCH 82/83] Add backers and sponsors from Open Collective Now your open collective backers and sponsors can to appear directly on your README. see how it'll look [here](https://github.com/apex/apex#backers) [More info](https://github.com/opencollective/opencollective/wiki/Github-banner) Also add badges on top. --- README.md | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/README.md b/README.md index 86ce5ddd4d..6ba113e922 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,9 @@ [![GitHub forks](https://img.shields.io/github/forks/JimBobSquarePants/ImageSharp.svg)](https://github.com/JimBobSquarePants/ImageSharp/network) [![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| @@ -126,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)] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 185b8cd3ae21a87ab8e260ff945f65bcbdd8fb1d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 5 Apr 2017 00:38:18 +1000 Subject: [PATCH 83/83] Fix resize banding bug --- .../Transforms/ResamplingWeightedProcessor.Weights.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs index 24d898fee9..99b143de67 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs @@ -168,7 +168,7 @@ namespace ImageSharp.Processing.Processors /// The weights public WeightsWindow GetWeightsWindow(int destIdx, int leftIdx, int rightIdx) { - BufferSpan span = this.dataBuffer.GetRowSpan(destIdx).Slice(leftIdx, rightIdx - leftIdx); + BufferSpan span = this.dataBuffer.GetRowSpan(destIdx).Slice(leftIdx, rightIdx - leftIdx + 1); return new WeightsWindow(leftIdx, span); } }