From e6e97ed4041cf2b6ef1aa6920be7cc4af97868d7 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 24 Mar 2017 07:49:10 +0000 Subject: [PATCH 01/27] 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 7950d260c..e97eaed58 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 e32e808c1..ec3b8ebe7 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 49be75139..16906c1fa 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 000000000..7a15e30e9 --- /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 000000000..091edd343 --- /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 000000000..98bc45f5b --- /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 cdb31ab69..4ec0fb507 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 000000000..8d5e77554 --- /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 bcccd1b44..1c960e0e8 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 02/27] 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 e97eaed58..498ae578c 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 db9686f3d..03994bc94 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 16906c1fa..31b14601a 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 7a15e30e9..d4e8534a7 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 091edd343..ee9359c2a 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 8d5e77554..7e4401635 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 03/27] 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 ee9359c2a..0462506a6 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 04/27] 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 d4e8534a7..924903039 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 0462506a6..41b884dd4 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 7e4401635..89f46a947 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 4ec0d1ee3c23f17e2a9a484f0b04fc7e80316af8 Mon Sep 17 00:00:00 2001 From: Johannes Bildstein Date: Sun, 26 Mar 2017 17:14:47 +0200 Subject: [PATCH 05/27] 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 084102728..cf9fc1a87 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 d12d0b976..e21d3d3db 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 3b2028afd..ef026f00c 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 000000000..0858acfed --- /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 000000000..b46a453a4 --- /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 000000000..b3e0133e4 --- /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 000000000..93b49558a --- /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 812823e7a..06b88dbc9 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 63ebe18a3..b6c821e3d 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 000000000..4cdf9122a --- /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 2030c3dca..06962e010 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 000000000..143ae00e9 --- /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 000000000..5ff47409b --- /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 fe7662306..7fd7a97d4 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 000000000..be3ae3f47 --- /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 72e88f3a33c8669b8a14b950ed7370e272910cd6 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 26 Mar 2017 22:26:03 +0100 Subject: [PATCH 06/27] 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 ad4d2cc98..61067e53b 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 7975f9b7e..a641cfa4d 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 1593014ae..9a6750872 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 4ec0fb507..5a73bbcc9 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 89f46a947..39ce61495 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 1c960e0e8..9fd33d90b 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 07/27] 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 91f383dd2..97947a787 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 000000000..e2bc2bd2d --- /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 08/27] 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 61067e53b..6dc0d89c5 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 a641cfa4d..bc18209f3 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 5a73bbcc9..26192ba1e 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 09/27] 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 924903039..882f903d6 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 10/27] 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 31b14601a..51cb0cdc0 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 4231a109953009ea162df2e5e9c9440b61b58573 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 28 Mar 2017 02:26:18 +0200 Subject: [PATCH 11/27] 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 000000000..e912ea29f --- /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 12/27] 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 718705b39..ace929bd6 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 2fa5fe43f..1b5df7574 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 ffcc5ee75..e3716124e 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 9a616d408..80a3e6793 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 cda702270..74f5cb717 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 77d943478..2975d4ad9 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 b34c1e88b..46c24be6f 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 f33ac25a6..74229a914 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 56f304070..65a5e7a5f 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 816401d4e..becc4d072 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 802df7c1d..167a1e786 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 2517ef7a8..e1a559c32 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 ecc1bca5a..921158174 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 53618312c..c81a55c0b 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 66f4a52bc..1d655ec32 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 b34977e2d..e171c9528 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 2fa6aad4b..a7e2e0e91 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 23fce0173..9f3aa405e 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 b59cf54f5..224b267e4 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 000000000..2ee700789 --- /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 77608e02b..fa06a863e 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 3568afe41..d928eb1a4 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 c5302bad0..957955c6c 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 bb54c1f98..6eeb7398a 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 6eda3e42a..40d6d94ac 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 255124a75..1374e5815 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 eb206380c..16e0b6635 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 3dc37e52d..4be938c39 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 0e6540d42..ccf8da3df 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 13/27] 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 000000000..7f3fb77d0 --- /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 14/27] 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 cf9fc1a87..debe1706c 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 b6c821e3d..81ed8d55c 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 143ae00e9..50a86d3cc 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 be3ae3f47..fa8b2a1a2 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 15/27] 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${} 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 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 2df3cc80c..cd4dfa117 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 16/27] 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 17/27] 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 86ce5ddd4..6ba113e92 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 18/27] 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 24d898fee..99b143de6 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); } } From 90e1c31242290281851bb321762a9e791e0a81a2 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Wed, 5 Apr 2017 08:59:06 +0100 Subject: [PATCH 19/27] fix loading from unseekable stream fixes #164 --- src/ImageSharp/Image.FromStream.cs | 4 +- .../ImageSharp.Tests/Image/ImageLoadTests.cs | 14 ++++++ .../Image/NoneSeekableStream.cs | 50 +++++++++++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 tests/ImageSharp.Tests/Image/NoneSeekableStream.cs diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index 41ac7757e..112b8a109 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -201,7 +201,7 @@ namespace ImageSharp { config = config ?? Configuration.Default; - Image img = WithSeekableStream(stream, s => Decode(stream, options, config)); + Image img = WithSeekableStream(stream, s => Decode(s, options, config)); if (img != null) { @@ -238,7 +238,7 @@ namespace ImageSharp stream.CopyTo(ms); ms.Position = 0; - return action(stream); + return action(ms); } } } diff --git a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs index ddb9414cc..10b0cbb94 100644 --- a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs @@ -86,6 +86,20 @@ namespace ImageSharp.Tests } + [Fact] + public void LoadFromNoneSeekableStream() + { + NoneSeekableStream stream = new NoneSeekableStream(this.DataStream); + Image img = Image.Load(stream); + + Assert.NotNull(img); + Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); + + + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, Configuration.Default); + + } + [Fact] public void LoadFromStreamWithType() { diff --git a/tests/ImageSharp.Tests/Image/NoneSeekableStream.cs b/tests/ImageSharp.Tests/Image/NoneSeekableStream.cs new file mode 100644 index 000000000..bc36b60eb --- /dev/null +++ b/tests/ImageSharp.Tests/Image/NoneSeekableStream.cs @@ -0,0 +1,50 @@ +using System; +using System.IO; + +namespace ImageSharp.Tests +{ + internal class NoneSeekableStream : Stream + { + private Stream dataStream; + + public NoneSeekableStream(Stream dataStream) + { + this.dataStream = dataStream; + } + + public override bool CanRead => this.dataStream.CanRead; + + public override bool CanSeek => false; + + public override bool CanWrite => false; + + public override long Length => this.dataStream.Length; + + public override long Position { get => this.dataStream.Position; set => throw new NotImplementedException(); } + + public override void Flush() + { + this.dataStream.Flush(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + return this.dataStream.Read(buffer, offset, count); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotImplementedException(); + } + + public override void SetLength(long value) + { + throw new NotImplementedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file From f91d6b513da93fb09158487abd9c61d0d2a64592 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Wed, 5 Apr 2017 09:29:51 +0100 Subject: [PATCH 20/27] bump SixLabors.Shapes version for perf increase --- src/ImageSharp.Drawing/ImageSharp.Drawing.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj index 351764bb9..259ae4b0f 100644 --- a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj +++ b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj @@ -40,7 +40,7 @@ All - + ..\..\ImageSharp.ruleset From a7b878d8d239b2658d0150a0efd0bb5404fc279f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 5 Apr 2017 23:55:29 +1000 Subject: [PATCH 21/27] Fix indexed png alpha selection. Fix #163 #165 --- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 9 +++++++-- src/ImageSharp/Quantizers/QuantizedImage.cs | 1 - 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 498ae578c..552673179 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -516,9 +516,14 @@ namespace ImageSharp.Formats colorTable[offset + 1] = bytes[1]; colorTable[offset + 2] = bytes[2]; - if (alpha <= this.options.Threshold) + if (alpha < 255 && alpha <= this.options.Threshold) { - transparentPixels.Add((byte)offset); + // Ensure the index is actually being used in our array. + // I'd like to find a faster way of doing this. + if (quantized.Pixels.Contains((byte)i)) + { + transparentPixels.Add((byte)i); + } } } diff --git a/src/ImageSharp/Quantizers/QuantizedImage.cs b/src/ImageSharp/Quantizers/QuantizedImage.cs index 528da7717..471abbae7 100644 --- a/src/ImageSharp/Quantizers/QuantizedImage.cs +++ b/src/ImageSharp/Quantizers/QuantizedImage.cs @@ -6,7 +6,6 @@ namespace ImageSharp.Quantizers { using System; - using System.Threading.Tasks; ///

/// Represents a quantized image where the pixels indexed by a color palette. From 7cdc970ea9178c2c5db79329461295c1215b3d41 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 6 Apr 2017 00:05:46 +1000 Subject: [PATCH 22/27] Update readme [skip ci] --- README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6ba113e92..ef37e4e91 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,13 @@ -# ImageSharp +# ImageSharp ImageSharp -**ImageSharp** is a new cross-platform 2D graphics API designed to allow the processing of images without the use of `System.Drawing`. +**ImageSharp** is a new ImageSharp is a fully featured, fully managed, cross-platform, 2D graphics API designed to allow the processing of images without the use of `System.Drawing`. -> **ImageSharp is still in early stages (alpha) but progress has been pretty quick. As such, please do not use on production environments until the library reaches release candidate status. Pre-release downloads are available from the [MyGet package repository](https://www.myget.org/gallery/imagesharp).** +Built against .Net Standard 1.1 ImageSharp can be used in device, cloud, and embedded/IoT scenarios. + +> **ImageSharp has made excellent progress and contains many great features but is still considered by us to be still in early stages (alpha). As such, we cannot support its use on production environments until the library reaches release candidate status. +> +> Pre-release downloads are available from the [MyGet package repository](https://www.myget.org/gallery/imagesharp).** [![GitHub license](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/JimBobSquarePants/ImageSharp/master/APACHE-2.0-LICENSE.txt) [![GitHub issues](https://img.shields.io/github/issues/JimBobSquarePants/ImageSharp.svg)](https://github.com/JimBobSquarePants/ImageSharp/issues) @@ -69,7 +73,9 @@ There's plenty there and more coming. Check out the [current features](features. ### API -Without the constraints of `System.Drawing` We have been able to develop something much more flexible, easier to code against, and much, much less prone to memory leaks. Gone are system-wide process-locks. Images and processors are thread safe usable in parallel processing utilizing all the availables cores. +Without the constraints of `System.Drawing` We have been able to develop something much more flexible, easier to code against, and much, much less prone to memory leaks. + +Gone are system-wide process-locks; ImageSharp images are thread-safe and fully supported in web environments. Many `Image` methods are also fluent. From 6b8b05aa8cbb133a69f944456c080be501809554 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 6 Apr 2017 00:06:35 +1000 Subject: [PATCH 23/27] Fix bold [skip ci] --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ef37e4e91..1a6995d46 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,9 @@ Built against .Net Standard 1.1 ImageSharp can be used in device, cloud, and embedded/IoT scenarios. -> **ImageSharp has made excellent progress and contains many great features but is still considered by us to be still in early stages (alpha). As such, we cannot support its use on production environments until the library reaches release candidate status. +> **ImageSharp** has made excellent progress and contains many great features but is still considered by us to be still in early stages (alpha). As such, we cannot support its use on production environments until the library reaches release candidate status. > -> Pre-release downloads are available from the [MyGet package repository](https://www.myget.org/gallery/imagesharp).** +> Pre-release downloads are available from the [MyGet package repository](https://www.myget.org/gallery/imagesharp). [![GitHub license](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/JimBobSquarePants/ImageSharp/master/APACHE-2.0-LICENSE.txt) [![GitHub issues](https://img.shields.io/github/issues/JimBobSquarePants/ImageSharp.svg)](https://github.com/JimBobSquarePants/ImageSharp/issues) From 67c54e4adff7fb18b6bfc812918476dc23b701d3 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Thu, 6 Apr 2017 09:27:40 +0100 Subject: [PATCH 24/27] Fix for off by one for indexed pngs --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index d6529940e..5ce9b5eb0 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -579,7 +579,7 @@ namespace ImageSharp.Formats // channel and we should try to read it. for (int x = 0; x < this.header.Width; x++) { - int index = newScanline[x]; + int index = newScanline[x + 1]; int pixelOffset = index * 3; byte a = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : (byte)255; @@ -603,7 +603,7 @@ namespace ImageSharp.Formats { for (int x = 0; x < this.header.Width; x++) { - int index = newScanline[x]; + int index = newScanline[x + 1]; int pixelOffset = index * 3; byte r = this.palette[pixelOffset]; @@ -982,4 +982,4 @@ namespace ImageSharp.Formats } } } -} \ No newline at end of file +} From be363c9a279385bef846fd042a7a70885b8f8431 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 6 Apr 2017 19:44:10 +1000 Subject: [PATCH 25/27] Fix duplicate [skip ci] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1a6995d46..6d37dd5e7 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # ImageSharp ImageSharp -**ImageSharp** is a new ImageSharp is a fully featured, fully managed, cross-platform, 2D graphics API designed to allow the processing of images without the use of `System.Drawing`. +**ImageSharp** is a new, fully featured, fully managed, cross-platform, 2D graphics API designed to allow the processing of images without the use of `System.Drawing`. Built against .Net Standard 1.1 ImageSharp can be used in device, cloud, and embedded/IoT scenarios. From 2c373a623b821cae3113aef3bba7207e2e6fb904 Mon Sep 17 00:00:00 2001 From: Steffen Habermehl Date: Thu, 6 Apr 2017 14:36:05 +0200 Subject: [PATCH 26/27] Issue #132: Handle undefined EXIF data type where no number of components set --- .../MetaData/Profiles/Exif/ExifReader.cs | 7 ++++++ .../Profiles/Exif/ExifProfileTests.cs | 22 ++++++++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 1 + .../Formats/Jpg/baseline/ExifUndefType.jpg | Bin 0 -> 6582 bytes 4 files changed, 30 insertions(+) create mode 100644 tests/ImageSharp.Tests/TestImages/Formats/Jpg/baseline/ExifUndefType.jpg diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs index 0b845c491..58f32bdd8 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs @@ -319,6 +319,13 @@ namespace ImageSharp uint numberOfComponents = this.GetLong(); + // Issue #132: ExifDataType == Undefined is treated like a byte array. + // If numberOfComponents == 0 this value can only be handled as an inline value and must fallback to 4 (bytes) + if (dataType == ExifDataType.Undefined && numberOfComponents == 0) + { + numberOfComponents = 4; + } + uint size = numberOfComponents * ExifValue.GetSize(dataType); byte[] data = this.GetBytes(4); diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs index 1bc31286d..2c902d3b8 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs @@ -7,9 +7,11 @@ namespace ImageSharp.Tests { using System; using System.Collections; + using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; + using ImageSharp.Processing; using Xunit; public class ExifProfileTests @@ -268,6 +270,26 @@ namespace ImageSharp.Tests } } + [Fact] + public void ExifTypeUndefined() + { + Image image = TestFile.Create(TestImages.Jpeg.Baseline.Bad.ExifUndefType).CreateImage(); + Assert.NotNull(image); + + ExifProfile profile = image.MetaData.ExifProfile; + Assert.NotNull(profile); + + IEnumerator enumerator = profile.Values.GetEnumerator(); + while (enumerator.MoveNext()) + { + ExifValue entry = enumerator.Current; + if (entry.DataType == ExifDataType.Undefined) + { + Assert.NotEqual(0, entry.NumberOfComponents); + } + } + } + private static ExifProfile GetExifProfile() { Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage(); diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index f0a0e8dd8..e9d658887 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -61,6 +61,7 @@ namespace ImageSharp.Tests public static class Bad { public const string MissingEOF = "Jpg/baseline/badeof.jpg"; + public const string ExifUndefType = "Jpg/baseline/ExifUndefType.jpg"; } public const string Cmyk = "Jpg/baseline/cmyk.jpg"; diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Jpg/baseline/ExifUndefType.jpg b/tests/ImageSharp.Tests/TestImages/Formats/Jpg/baseline/ExifUndefType.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3a7f29c7d1c4293f8a6734470de0ae50b3675e59 GIT binary patch literal 6582 zcmd^jWmuG3)b=w&_Y4gZ!_Xi&LkS4d5{iVhfOHAM&>={7OGyX}9SV3*QbMGqOGS`Q zk+k3-?=#9d$LqVkKkx5%&5nE4UTf`Mv-X_5JzD@^8rRgX0T2iTPy;`}*&-g7im#&` z0H~`A0Fh7t#{?i?{-;*3aka8VXgj!ix_UUcx*?Qqi6IOf@7cKecmN_Ow3swXR9Zw5 zftHXK6O%@xKurLUSETwsQ(pkGLkWJZ*eE#XymR~(#K>s8UwqtB@PZgz+~1T;1mJ@R zEh2(~ru^1j0KY?0f8z}hL(lOZi1E(xcM#*BW1L}vb4(0k!gCA*F;VJ&{SyCyN&dj3 z=kkoWtu7z}3jn~`ffW4K#j&C1SQif*!#NHDCqaq*Jx&BDM0Jk&Kn%nF*Du{U9|iIm z{@?>e|H(l9z_|V;L0NKu8uH%=m?6J=A_iM8oMRY>`Oaltc}17hY|qbgn(!;(?I_l4K2vW$rOOuF$(|J&f!LcqqA8+3BZFw zaS0@RFcAkx@`m!zd`>lw@QudKjFBmX3~&oQi>wo|ciC zmW~!@1cC?Z;1iG#5RlMPkWtY7uj%X+04D}YfDd>OBmjj&@ZgZM*Wmmi02B{*ssANB zA}BrqAu+C%9&Grd1t$W5p1lXi@jxvY9=M>8+Sz!W<=vdljW$jwmO%7;Z_@Iip zn=t|#Le3hWSJylb734t~NLkZr5}ZBkq45`%UGBardV!?-b!UJ;wAIeUVe__?`5lf^ zubgC@Imp0PtZqQE-ffA3@`c9kOLXt~8eI#O&6{wI|39Jv_ktf>Z9?!AfCuTHErCG6 z(?kfTqo<~!GT;TQ)gPR5+fSv)?0j@~B&kd@#iYi9an`&-lr{=OhDsN6O zpdo$6%WqjyChSr{WjmLiVdNxftd%F&_pm0NS4+g8Ou|TS(}vP>$cH5R9u3ra9bxTV zRUh^hD=x&39uJfyqz+?kw|Tc+_$5`$EF!>Ldv$q#jh!N#jKQ+NH%(QCh|LPW$hohX z>gKyJg)Ildu+eb}VBDJgZ`PBCi8I#&Vy7sD3?#damc@hXww4G~fyfc!$SLy3Sz4EU zT%rBOc`szF1LgY#lPGRNMPH5X7U*2kzs3-y&)2A8vGB!GQdXVxy>B}4NdPi(;D@q8 z{}NvRa-zoQiYBzpxfgL)b006@T7+AIPifClg3t8P#~h+$38fSm`9QJ$j3F9T?Z`D21-vy^Sc9=w;f$5Y;aU&|T?Uw(&|WJ4*QRa{eRR`Z45QEgY^ z%1{z_;mR|aQq_#ULq;}d%vvvriNT#h^e)>ao$7I+C+P+k2ItgOhiH%5^e&C5CZCD7eib%5?eXre}}n zLoqV$wV30vI>Y3578Du4ySqKTrV;{UwFZ>32Al>;k?o_8xKT?r99fII?yp-Dv~Rmg zL_epuWxbhUHKO6cW9If3zR;A32FhE6;HDCf*vM%A%xn=qkWXJN+}Y$sb#>zC;hbuW z_^sHdjx$X<=w-bmyYEu69K4hHyY;~a>8ru&1i`H|A=ObtN51eZxug$7`-dGrWs|9v zH?6-PIH>lD=+&2%$bFUrZ@>jOc&`6D{oqA`0}vPjC9nPK%!6J`4)WFh<>MP~cF}Mw z5{Z-&6_r(1R-MQBIZzh@dT*TnJ|{Q?#(_edD?@S0CyzE;Uu$~r%+^mTPcDn=8^%^P zR<`p+$E@n>KL`M=D1dhmABs;*LQ3?HU&P%(I5h`aj7CIMJSFwvI01|nsi1=?97Ax* zE9zQ?V2kSLxGb#P7ne}a&hI9n9Q4#`3$~R!=!8{Q7_j(zIlG5!_>xhN^p|++8Y3{V z%X)jo+uok088%26V7*L1QlP8yB1(4|{lsEY-EDH7>r{n%eE&2(av^}4G{38jarri|aLbQOuqI&$fP z3y+#o;$(J-_VtR(rWn40T;8e=l!Z9U!^o=GPWmKwt=$#=8B5(VI>$EyRvRY0OTrP= zLCB3fYX;>_w3mGI3*n&+2D>A&7JotoW-t-Vy@Y<65Al|7;Z={TyUQNk5~$)E*D!e} zx`k~jh!atN(!csW-tff$V~tCipv%&cyxg7eBBI*|wPC(P+c+nThC1#N6s*b`;xk$u`Sl68}ZaU!ak70H2;=Cl^&6*mg{ubU*Q3TV`}5Gx+IcIW$!-Y;p0 z9?!j_w>r&OiZQ~|sIBnNjjgPUt0}*K|KU<5jja@Q-`HHwp$i@Nd-d&VyT&1sd8VhL ziawEd<3|$~vzLBqF6JQHzyd2)j7P&-J7nyN)^LY{&X*fQyRBYgqP2NJUf;5=!s(MGPQxCf0L1IO0~$)Hu+!vuN*~m zyfO85+=T8(_v-IN!0TN_-n zL0(7xSe_&zjwz*dUd@J04tz7G^tMIPqZ?#5>S)a!-^d*EnkhuGL@MwghT{?{%*>CH zg$3BE!oB>-K2o*Qhmbd@y;+r zD}|l6;&PcGRhh=9npyc#FXZsly^popek4?}h!09}df@bzV*AOsa&V z!?Z{b*ZD0e$4sw-cbRfK%SBkG%viU)Jj3INvFOtoFox+WA=PkmxYbV%er zYEHtyunn`Go4Tb9hId|dL}&gGQ*2I`7CHl{NkR;Hcw#(jpJ(r6Jsf$Mdu*hHF$)@D zIj+JF3<^FHNLM1(EtwPz3!s^2>b$~!B)CXbbQSd z_k>RM`7=3qkhNV9qPA{l;n+dK<0KSW+irRWfId_Jd}u%+P<%pM*!jy9LjX7jT0zGW zGZvCk*txeaUk5{2pcelLWaK!`0IQsSa#!Iv~}LYrYEzM(z9XIb<9WA_=?_3A*pZ=FF8s(loe-tEU%sL=J?TFajhwsaoq zu6mKnW51QnpVc&?Mpo@ofLQU6{zp&7TmES|b|oAMl+j?18NUg^Bfk&Js%=aYTO zT~5P+{+%`o)Re(CYPYr;0`?VSX3MLSojIMOpfA+_%I;I_zgP~x8ItWO3! z(c)WZ>9?z!Dnav`bD?^yEtApNO%!UerpPT9db%mMMUe|7J2XWVCJ#BjkYqPh8O_gK z{v6GA26&!a-K_GPn-~%_-^h{p@;UnIuII_{rZQE@%S96hlb<%civG*Ai z48;Ra9o?D~%HA>Um*0!9twi=jrtWe&Dhc>r;3h!|)pcBSi&>sGboR%*9=1&Tew4QG zX&y2A?9LM0SMk0?QV2Vj9)`~EZS=;=QcYwHsn{cO)2yfd?e+&zv_K?B^-&u61S5AG zLUlGwqb7%t{RM?{CX(kOk6n#U+!@ehUKh~QSm*Nc7~5~p)mJ&^ndF`&8D8;b+EILE zqm!JQX>ejFBOuf%Sl3|gl~L~4^(hv9{Rit?(iLBfGF>lyvUBR6xEcnZ8PF=!!rn;F z!FoI*$Pb-g>wD4nSBf>NcbrINvf03v(eWOzb%9b8eH-1568P5ShuwN~FJ{Fw@YJm< zwwZz>cae{~v8%YyblEUMsifn|#fuLJDkK<+zE{N;-K}3=7Rg@^xVskoZv7I)+r;UHJ;e=&i#KVu-qQBESomKvR~T-KORV*4 z6WOn}oodcRmP>R)0=Vx;aAfT$b%gZmbH~5XtCLwBe%9~HBuypw#+ zVX-c9R2K!9WWq;|Q$rZxMv<-~ReKe&f*i#h$E{aS_|a4O31~V)P-TGfy0hFb#j6V8 z#or#_j2sM&hFU{^F)uc+$M5}X`TqsK0!0A#xcwte1R!u2N{8c*C=>RL+2temdmWkD zP|{eCq7jMxpBmuLV)^2+v3$?Mmuz3_@%pubKe5>uScXBkOsgDg9mosui@mXZC!*IFVH{&yJh}-DeF=6LiiBkY+4-(eO~se=qDdcqk=Fk@LgBw;OO8wcQ)6| zTjUNpu8n6v>(giEJ3D#s5g`k#@2gMiDMj%%)%ulJ5*vHWl(H1F-G&X0vIKet zgDsxxRh4oH8cVF1HfHrHT9GwFREU!y6B<(VBCKu^v5-L{V~+m$gUSNHWH+;1)_Ylk zvhj;&yX5qQ7WuTvtYH@!KzLFr!dk#k)w)3EUFrMxs_q<5;idkgGl0r4xi~n&|5+m@ z3I2M-?eUU**f$1|(!3dV;hckITCFnH!a%84gbJ|Xl{*bm5uZA(!`bVIx-zBAl9pM{ zfESVu7T~X;trw;U%5lm`!LrZ&g{mTRoj7PD5JirM&QtUkB4co+`$t51a!pbJkfEz}v5vXe+0Y6Q3Lf@UgG1hY@78AXuXQ5M3_AYNw*gbzX2I_8N?SOVD10m>Z;P!cmZ>m%vEAxc8e8ibdNlqGm~0qm(YhAY82OCi!YKA$jG8Qq}_Jd zdpJ8M4uAAkQ)~M50kfB9YuPmX=&KOAeg7wy!Ip1q@|&9 z_FtS`qsN^fo&xlGU2!3|Wb)`6a_`xX5BG)V`bmz$zDc3AeDphX`RYGY(lGy(zZjkK zgt(~JW20m7m9eC(42DSm{tcFWsI&5JvXk!fDZj24Or(vQ>2HP#2UhE)p8ouRi8Uvp z&Azv>+{dc9Y@Fo749NLV7COE~)l1Aw4!-hu7cO`Wg;zt$%;A*(_v

AR}I~(<=*8 zoGm|4%RCdNy|NfYh{(NmQ_UEZeZWV-UZM7fG;}Ddz0-YujgB**jaVX2E}ueXgE=6- zF4wDG+l0N+x-5HuC}Uv~-_*~DD_=V{JfZf(BgwioFJ;!Xm6pCYd|urA62zl5Hyl-Y z%wa7Y2HClmNYATV+oL5Kn`#Aqcg33HSxRrGhU=TR($JYi&z`LIJmBz!d{o~(VRM^1 zVm&dnP4WvE>k*=>N)*eYzPZ2*rKBlJO&7w~O{mM0a_WObRIoJhIQ{kc6-Gr9J3Sjd zzn~}3)hFKHs>@5Rz#JAqChD}sVBIEPI!n_7yojGXMqNIkEz#+REA{1}xa4!0+cF7$ zbVc0VdP|vqw3AJsB^O09c)YU0J|ik_dOwl`u50t)xDU^|4oDL?kKUq+)8O#4n68jeB~V+WzIP9;g+TM zYo%;of_VlrnO%>&CDL1B9#{%yUVs~4R4z%)Ldbie=#4}Asa&tzD;|r}|H8nBszDtu oymR%I(k~lbp%TpWnIK~BZ1YSb5Wg*z$j~QpyCCZ4`q{hx0ked5!2kdN literal 0 HcmV?d00001 From 13a4a2613feef13c473a3f2688febb539c3c4902 Mon Sep 17 00:00:00 2001 From: Steffen Habermehl Date: Fri, 7 Apr 2017 05:59:12 +0200 Subject: [PATCH 27/27] Updates unit test for undefined EXIF types --- .../MetaData/Profiles/Exif/ExifProfileTests.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs index 2c902d3b8..f380724df 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs @@ -7,11 +7,9 @@ namespace ImageSharp.Tests { using System; using System.Collections; - using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; - using ImageSharp.Processing; using Xunit; public class ExifProfileTests @@ -279,13 +277,11 @@ namespace ImageSharp.Tests ExifProfile profile = image.MetaData.ExifProfile; Assert.NotNull(profile); - IEnumerator enumerator = profile.Values.GetEnumerator(); - while (enumerator.MoveNext()) + foreach (ExifValue value in profile.Values) { - ExifValue entry = enumerator.Current; - if (entry.DataType == ExifDataType.Undefined) + if (value.DataType == ExifDataType.Undefined) { - Assert.NotEqual(0, entry.NumberOfComponents); + Assert.Equal(4, value.NumberOfComponents); } } }