diff --git a/.travis.yml b/.travis.yml index 70501a484..54e4dee2f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ matrix: - os: linux # Ubuntu 14.04 dist: trusty sudo: required - dotnet: 1.0.4 + dotnet: 2.1.4 mono: latest # - os: osx # OSX 10.11 # osx_image: xcode7.3.1 @@ -21,7 +21,7 @@ branches: script: - git submodule -q update --init - dotnet restore - - dotnet test tests/ImageSharp.Tests/ImageSharp.Tests.csproj -c Release -f "netcoreapp1.1" + - dotnet test tests/ImageSharp.Tests/ImageSharp.Tests.csproj -c Release -f "netcoreapp2.0" env: global: diff --git a/.vscode/launch.json b/.vscode/launch.json index c9c7453f6..c772e647c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,7 @@ "request": "launch", "preLaunchTask": "build", // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceRoot}/samples/AvatarWithRoundedCorner/bin/Debug/netcoreapp1.1/AvatarWithRoundedCorner.dll", + "program": "${workspaceRoot}/tests/ImageSharp.Benchmarks/bin/Debug/netcoreapp2.0/ImageSharp.Benchmarks.dll", "args": [], "cwd": "${workspaceRoot}/samples/AvatarWithRoundedCorner", // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 4a7b35ac2..82aaa2f8d 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -16,13 +16,13 @@ { "taskName": "build benchmark", "suppressTaskName": true, - "args": [ "build", "tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj", "-f", "netcoreapp1.1", "-c", "Release" ], + "args": [ "build", "tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj", "-f", "netcoreapp2.0", "-c", "Release" ], "showOutput": "always", "problemMatcher": "$msCompile" }, { "taskName": "test", - "args": ["tests/ImageSharp.Tests/ImageSharp.Tests.csproj", "-c", "release", "-f", "netcoreapp1.1"], + "args": ["tests/ImageSharp.Tests/ImageSharp.Tests.csproj", "-c", "release", "-f", "netcoreapp2.0"], "isTestCommand": true, "showOutput": "always", "problemMatcher": "$msCompile" diff --git a/ImageSharp.sln b/ImageSharp.sln index 4ea89dd45..3ff5b09d4 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -43,12 +43,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Tests", "tests\I EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Benchmarks", "tests\ImageSharp.Benchmarks\ImageSharp.Benchmarks.csproj", "{2BF743D8-2A06-412D-96D7-F448F00C5EA5}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{7CC6D57E-B916-43B8-B315-A0BB92F260A2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AvatarWithRoundedCorner", "samples\AvatarWithRoundedCorner\AvatarWithRoundedCorner.csproj", "{844FC582-4E78-4371-847D-EFD4D1103578}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChangeDefaultEncoderOptions", "samples\ChangeDefaultEncoderOptions\ChangeDefaultEncoderOptions.csproj", "{07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Sandbox46", "tests\ImageSharp.Sandbox46\ImageSharp.Sandbox46.csproj", "{561B880A-D9EE-44EF-90F5-817C54A9D9AB}" EndProject Global @@ -112,30 +106,6 @@ Global {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x64.Build.0 = Release|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x86.ActiveCfg = Release|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x86.Build.0 = Release|Any CPU - {844FC582-4E78-4371-847D-EFD4D1103578}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {844FC582-4E78-4371-847D-EFD4D1103578}.Debug|Any CPU.Build.0 = Debug|Any CPU - {844FC582-4E78-4371-847D-EFD4D1103578}.Debug|x64.ActiveCfg = Debug|Any CPU - {844FC582-4E78-4371-847D-EFD4D1103578}.Debug|x64.Build.0 = Debug|Any CPU - {844FC582-4E78-4371-847D-EFD4D1103578}.Debug|x86.ActiveCfg = Debug|Any CPU - {844FC582-4E78-4371-847D-EFD4D1103578}.Debug|x86.Build.0 = Debug|Any CPU - {844FC582-4E78-4371-847D-EFD4D1103578}.Release|Any CPU.ActiveCfg = Release|Any CPU - {844FC582-4E78-4371-847D-EFD4D1103578}.Release|Any CPU.Build.0 = Release|Any CPU - {844FC582-4E78-4371-847D-EFD4D1103578}.Release|x64.ActiveCfg = Release|Any CPU - {844FC582-4E78-4371-847D-EFD4D1103578}.Release|x64.Build.0 = Release|Any CPU - {844FC582-4E78-4371-847D-EFD4D1103578}.Release|x86.ActiveCfg = Release|Any CPU - {844FC582-4E78-4371-847D-EFD4D1103578}.Release|x86.Build.0 = Release|Any CPU - {07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Debug|x64.ActiveCfg = Debug|Any CPU - {07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Debug|x64.Build.0 = Debug|Any CPU - {07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Debug|x86.ActiveCfg = Debug|Any CPU - {07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Debug|x86.Build.0 = Debug|Any CPU - {07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Release|Any CPU.Build.0 = Release|Any CPU - {07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Release|x64.ActiveCfg = Release|Any CPU - {07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Release|x64.Build.0 = Release|Any CPU - {07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Release|x86.ActiveCfg = Release|Any CPU - {07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Release|x86.Build.0 = Release|Any CPU {561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Debug|Any CPU.Build.0 = Debug|Any CPU {561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -158,8 +128,6 @@ Global {2E33181E-6E28-4662-A801-E2E7DC206029} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {2BF743D8-2A06-412D-96D7-F448F00C5EA5} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} - {844FC582-4E78-4371-847D-EFD4D1103578} = {7CC6D57E-B916-43B8-B315-A0BB92F260A2} - {07EE511D-4BAB-4323-BAFC-3AF2BF9366F0} = {7CC6D57E-B916-43B8-B315-A0BB92F260A2} {561B880A-D9EE-44EF-90F5-817C54A9D9AB} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution diff --git a/samples/AvatarWithRoundedCorner/AvatarWithRoundedCorner.csproj b/samples/AvatarWithRoundedCorner/AvatarWithRoundedCorner.csproj deleted file mode 100644 index e000aacf1..000000000 --- a/samples/AvatarWithRoundedCorner/AvatarWithRoundedCorner.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - Exe - netcoreapp1.1 - - - - - - - \ No newline at end of file diff --git a/samples/AvatarWithRoundedCorner/Program.cs b/samples/AvatarWithRoundedCorner/Program.cs deleted file mode 100644 index 087bbc29d..000000000 --- a/samples/AvatarWithRoundedCorner/Program.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.Primitives; -using SixLabors.Shapes; - -namespace AvatarWithRoundedCorner -{ - static class Program - { - static void Main(string[] args) - { - System.IO.Directory.CreateDirectory("output"); - using (var img = Image.Load("fb.jpg")) - { - // as generate returns a new IImage make sure we dispose of it - using (Image destRound = img.Clone(x => x.ConvertToAvatar(new Size(200, 200), 20))) - { - destRound.Save("output/fb.png"); - } - - using (Image destRound = img.Clone(x => x.ConvertToAvatar(new Size(200, 200), 100))) - { - destRound.Save("output/fb-round.png"); - } - - using (Image destRound = img.Clone(x => x.ConvertToAvatar(new Size(200, 200), 150))) - { - destRound.Save("output/fb-rounder.png"); - } - - using (Image destRound = img.CloneAndConvertToAvatarWithoutApply(new Size(200, 200), 150)) - { - destRound.Save("output/fb-rounder-without-apply.png"); - } - - // the original `img` object has not been altered at all. - } - } - - // 1. The short way: - // Implements a full image mutating pipeline operating on IImageProcessingContext - // We need the dimensions of the resized image to deduce 'IPathCollection' needed to build the corners, - // so we implement an "inline" image processor by utilizing 'ImageExtensions.Apply()' - private static IImageProcessingContext ConvertToAvatar(this IImageProcessingContext processingContext, Size size, float cornerRadius) - { - return processingContext.Resize(new ResizeOptions - { - Size = size, - Mode = ResizeMode.Crop - }).Apply(i => ApplyRoundedCorners(i, cornerRadius)); - } - - // 2. A more verbose way, avoiding 'Apply()': - // First we create a resized clone of the image, then we draw the corners on that instance with Mutate(). - private static Image CloneAndConvertToAvatarWithoutApply(this Image image, Size size, float cornerRadius) - { - Image result = image.Clone( - ctx => ctx.Resize( - new ResizeOptions - { - Size = size, - Mode = ResizeMode.Crop - })); - - ApplyRoundedCorners(result, cornerRadius); - return result; - } - - // This method can be seen as an inline implementation of an `IImageProcessor`: - // (The combination of `IImageOperations.Apply()` + this could be replaced with an `IImageProcessor`) - public static void ApplyRoundedCorners(Image img, float cornerRadius) - { - IPathCollection corners = BuildCorners(img.Width, img.Height, cornerRadius); - - // mutating in here as we already have a cloned original - img.Mutate(x => x.Fill(Rgba32.Transparent, corners, new GraphicsOptions(true) - { - BlenderMode = PixelBlenderMode.Src // enforces that any part of this shape that has color is punched out of the background - })); - } - - public static IPathCollection BuildCorners(int imageWidth, int imageHeight, float cornerRadius) - { - // first create a square - var rect = new RectangularePolygon(-0.5f, -0.5f, cornerRadius, cornerRadius); - - // then cut out of the square a circle so we are left with a corner - IPath cornerToptLeft = rect.Clip(new EllipsePolygon(cornerRadius - 0.5f, cornerRadius - 0.5f, cornerRadius)); - - // corner is now a corner shape positions top left - //lets make 3 more positioned correctly, we can do that by translating the orgional artound the center of the image - var center = new Vector2(imageWidth / 2F, imageHeight / 2F); - - float rightPos = imageWidth - cornerToptLeft.Bounds.Width + 1; - float bottomPos = imageHeight - cornerToptLeft.Bounds.Height + 1; - - // move it across the widthof the image - the width of the shape - IPath cornerTopRight = cornerToptLeft.RotateDegree(90).Translate(rightPos, 0); - IPath cornerBottomLeft = cornerToptLeft.RotateDegree(-90).Translate(0, bottomPos); - IPath cornerBottomRight = cornerToptLeft.RotateDegree(180).Translate(rightPos, bottomPos); - - return new PathCollection(cornerToptLeft, cornerBottomLeft, cornerTopRight, cornerBottomRight); - } - } -} \ No newline at end of file diff --git a/samples/AvatarWithRoundedCorner/fb.jpg b/samples/AvatarWithRoundedCorner/fb.jpg deleted file mode 100644 index 7241890e2..000000000 --- a/samples/AvatarWithRoundedCorner/fb.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:93bb4d6281dc1e845db57e836e0dca30b7a4062e81044efb27ad4d8b1a33130c -size 15787 diff --git a/samples/ChangeDefaultEncoderOptions/ChangeDefaultEncoderOptions.csproj b/samples/ChangeDefaultEncoderOptions/ChangeDefaultEncoderOptions.csproj deleted file mode 100644 index 5797be0f5..000000000 --- a/samples/ChangeDefaultEncoderOptions/ChangeDefaultEncoderOptions.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - Exe - netcoreapp1.1 - - - - - - - \ No newline at end of file diff --git a/samples/ChangeDefaultEncoderOptions/Program.cs b/samples/ChangeDefaultEncoderOptions/Program.cs deleted file mode 100644 index a8fbd7599..000000000 --- a/samples/ChangeDefaultEncoderOptions/Program.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Formats.Jpeg; - -namespace ChangeDefaultEncoderOptions -{ - class Program - { - static void Main(string[] args) - { - // lets switch out the default encoder for jpeg to one - // that saves at 90 quality and ignores the matadata - Configuration.Default.SetEncoder(ImageFormats.Jpeg, new JpegEncoder() - { - Quality = 90, - IgnoreMetadata = true - }); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs index b0d57f68b..fa8750d34 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs @@ -181,7 +181,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion Guard.NotNull(color, nameof(color)); // Conversion - return YCbCrAndRgbConverter.Convert(color); + Rgb rgb = YCbCrAndRgbConverter.Convert(color); + + // Adaptation + return this.Adapt(rgb); } } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/LinearRgbAndCieXyzConverterBase.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/LinearRgbAndCieXyzConverterBase.cs index 2b21a604f..f6b431585 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/LinearRgbAndCieXyzConverterBase.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/LinearRgbAndCieXyzConverterBase.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation internal abstract class LinearRgbAndCieXyzConverterBase { /// - /// Geturns the correct matrix to convert between the Rgb and CieXyz color space. + /// Returns the correct matrix to convert between the Rgb and CieXyz color space. /// /// The Rgb working space. /// The based on the chromaticity and working space. @@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation var vector = Vector3.Transform(workingSpace.WhitePoint.Vector, inverseXyzMatrix); - // Use transposed Rows/Coloumns + // Use transposed Rows/Columns // TODO: Is there a built in method for this multiplication? return new Matrix4x4 { diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuserBase.cs b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuserBase.cs index 46bafcc0c..8f448198b 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuserBase.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuserBase.cs @@ -70,22 +70,10 @@ namespace SixLabors.ImageSharp.Dithering.Base /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Dither(ImageFrame pixels, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY) + public void Dither(ImageFrame image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY) where TPixel : struct, IPixel { - this.Dither(pixels, source, transformed, x, y, minX, minY, maxX, maxY, true); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Dither(ImageFrame image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY, bool replacePixel) - where TPixel : struct, IPixel - { - if (replacePixel) - { - // Assign the transformed pixel to the array. - image[x, y] = transformed; - } + image[x, y] = transformed; // Calculate the error Vector4 error = source.ToVector4() - transformed.ToVector4(); diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs index c538d643c..dabc4e682 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs @@ -6,7 +6,7 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Dithering { /// - /// Encapsulates properties and methods required to perfom diffused error dithering on an image. + /// Encapsulates properties and methods required to perform diffused error dithering on an image. /// public interface IErrorDiffuser { @@ -25,25 +25,5 @@ namespace SixLabors.ImageSharp.Dithering /// The pixel format. void Dither(ImageFrame image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY) where TPixel : struct, IPixel; - - /// - /// Transforms the image applying the dither matrix. This method alters the input pixels array - /// - /// The image - /// The source pixel - /// The transformed pixel - /// The column index. - /// The row index. - /// The minimum column value. - /// The minimum row value. - /// The maximum column value. - /// The maximum row value. - /// - /// Whether to replace the pixel at the given coordinates with the transformed value. - /// Generally this would be true for standard two-color dithering but when used in conjunction with color quantization this should be false. - /// - /// The pixel format. - void Dither(ImageFrame image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY, bool replacePixel) - where TPixel : struct, IPixel; } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/KnownDiffusers.cs b/src/ImageSharp/Dithering/ErrorDiffusion/KnownDiffusers.cs new file mode 100644 index 000000000..c75530b8e --- /dev/null +++ b/src/ImageSharp/Dithering/ErrorDiffusion/KnownDiffusers.cs @@ -0,0 +1,56 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Dithering +{ + /// + /// Contains reusable static instances of known error diffusion algorithms + /// + public static class KnownDiffusers + { + /// + /// Gets the error diffuser that implements the Atkinson algorithm. + /// + public static IErrorDiffuser Atkinson { get; } = new AtkinsonDiffuser(); + + /// + /// Gets the error diffuser that implements the Burks algorithm. + /// + public static IErrorDiffuser Burks { get; } = new BurksDiffuser(); + + /// + /// Gets the error diffuser that implements the Floyd-Steinberg algorithm. + /// + public static IErrorDiffuser FloydSteinberg { get; } = new FloydSteinbergDiffuser(); + + /// + /// Gets the error diffuser that implements the Jarvis-Judice-Ninke algorithm. + /// + public static IErrorDiffuser JarvisJudiceNinke { get; } = new JarvisJudiceNinkeDiffuser(); + + /// + /// Gets the error diffuser that implements the Sierra-2 algorithm. + /// + public static IErrorDiffuser Sierra2 { get; } = new Sierra2Diffuser(); + + /// + /// Gets the error diffuser that implements the Sierra-3 algorithm. + /// + public static IErrorDiffuser Sierra3 { get; } = new Sierra3Diffuser(); + + /// + /// Gets the error diffuser that implements the Sierra-Lite algorithm. + /// + public static IErrorDiffuser SierraLite { get; } = new SierraLiteDiffuser(); + + /// + /// Gets the error diffuser that implements the Stevenson-Arce algorithm. + /// + public static IErrorDiffuser StevensonArce { get; } = new StevensonArceDiffuser(); + + /// + /// Gets the error diffuser that implements the Stucki algorithm. + /// + public static IErrorDiffuser Stucki { get; } = new StuckiDiffuser(); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/StevensonArceDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/StevensonArceDiffuser.cs new file mode 100644 index 000000000..0f0338ac7 --- /dev/null +++ b/src/ImageSharp/Dithering/ErrorDiffusion/StevensonArceDiffuser.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Dithering.Base; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Dithering +{ + /// + /// Applies error diffusion based dithering using the Stevenson-Arce image dithering algorithm. + /// + public sealed class StevensonArceDiffuser : ErrorDiffuserBase + { + /// + /// The diffusion matrix + /// + private static readonly Fast2DArray StevensonArceMatrix = + new float[,] + { + { 0, 0, 0, 0, 0, 32, 0 }, + { 12, 0, 26, 0, 30, 0, 16 }, + { 0, 12, 0, 26, 0, 12, 0 }, + { 5, 0, 12, 0, 12, 0, 5 } + }; + + /// + /// Initializes a new instance of the class. + /// + public StevensonArceDiffuser() + : base(StevensonArceMatrix, 200) + { + } + } +} diff --git a/src/ImageSharp/Dithering/Ordered/BayerDither.cs b/src/ImageSharp/Dithering/Ordered/BayerDither.cs deleted file mode 100644 index 685dca5fe..000000000 --- a/src/ImageSharp/Dithering/Ordered/BayerDither.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Dithering.Base; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Dithering -{ - /// - /// Applies error diffusion based dithering using the 4x4 Bayer dithering matrix. - /// - /// - public sealed class BayerDither : OrderedDitherBase - { - /// - /// The threshold matrix. - /// This is calculated by multiplying each value in the original matrix by 16 and subtracting 1 - /// - private static readonly Fast2DArray ThresholdMatrix = - new byte[,] - { - { 15, 143, 47, 175 }, - { 207, 79, 239, 111 }, - { 63, 191, 31, 159 }, - { 255, 127, 223, 95 } - }; - - /// - /// Initializes a new instance of the class. - /// - public BayerDither() - : base(ThresholdMatrix) - { - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/BayerDither2x2.cs b/src/ImageSharp/Dithering/Ordered/BayerDither2x2.cs new file mode 100644 index 000000000..1d844c8a7 --- /dev/null +++ b/src/ImageSharp/Dithering/Ordered/BayerDither2x2.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Dithering +{ + /// + /// Applies order dithering using the 2x2 Bayer dithering matrix. + /// + public sealed class BayerDither2x2 : OrderedDither + { + /// + /// Initializes a new instance of the class. + /// + public BayerDither2x2() + : base(2) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/BayerDither4x4.cs b/src/ImageSharp/Dithering/Ordered/BayerDither4x4.cs new file mode 100644 index 000000000..4e9f20beb --- /dev/null +++ b/src/ImageSharp/Dithering/Ordered/BayerDither4x4.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Dithering +{ + /// + /// Applies order dithering using the 4x4 Bayer dithering matrix. + /// + public sealed class BayerDither4x4 : OrderedDither + { + /// + /// Initializes a new instance of the class. + /// + public BayerDither4x4() + : base(4) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/BayerDither8x8.cs b/src/ImageSharp/Dithering/Ordered/BayerDither8x8.cs new file mode 100644 index 000000000..3ff179a06 --- /dev/null +++ b/src/ImageSharp/Dithering/Ordered/BayerDither8x8.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Dithering +{ + /// + /// Applies order dithering using the 8x8 Bayer dithering matrix. + /// + public sealed class BayerDither8x8 : OrderedDither + { + /// + /// Initializes a new instance of the class. + /// + public BayerDither8x8() + : base(8) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs b/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs index 689c9a85b..339f2861d 100644 --- a/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs +++ b/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs @@ -6,7 +6,7 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Dithering { /// - /// Encapsulates properties and methods required to perfom ordered dithering on an image. + /// Encapsulates properties and methods required to perform ordered dithering on an image. /// public interface IOrderedDither { @@ -17,12 +17,11 @@ namespace SixLabors.ImageSharp.Dithering /// The source pixel /// The color to apply to the pixels above the threshold. /// The color to apply to the pixels below the threshold. - /// The to pack/unpack to. - /// The component index to test the threshold against. Must range from 0 to 3. + /// The threshold to split the image. Must be between 0 and 1. /// The column index. /// The row index. /// The pixel format. - void Dither(ImageFrame image, TPixel source, TPixel upper, TPixel lower, ref Rgba32 rgba, int index, int x, int y) + void Dither(ImageFrame image, TPixel source, TPixel upper, TPixel lower, float threshold, int x, int y) where TPixel : struct, IPixel; } } \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/KnownDitherers.cs b/src/ImageSharp/Dithering/Ordered/KnownDitherers.cs new file mode 100644 index 000000000..e58cbad8a --- /dev/null +++ b/src/ImageSharp/Dithering/Ordered/KnownDitherers.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Dithering +{ + /// + /// Contains reusable static instances of known ordered dither matrices + /// + public class KnownDitherers + { + /// + /// Gets the order ditherer using the 2x2 Bayer dithering matrix + /// + public static IOrderedDither BayerDither2x2 { get; } = new BayerDither2x2(); + + /// + /// Gets the order ditherer using the 3x3 dithering matrix + /// + public static IOrderedDither OrderedDither3x3 { get; } = new OrderedDither3x3(); + + /// + /// Gets the order ditherer using the 4x4 Bayer dithering matrix + /// + public static IOrderedDither BayerDither4x4 { get; } = new BayerDither4x4(); + + /// + /// Gets the order ditherer using the 8x8 Bayer dithering matrix + /// + public static IOrderedDither BayerDither8x8 { get; } = new BayerDither8x8(); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/OrderedDither.cs b/src/ImageSharp/Dithering/Ordered/OrderedDither.cs index 12968914d..c07b185bb 100644 --- a/src/ImageSharp/Dithering/Ordered/OrderedDither.cs +++ b/src/ImageSharp/Dithering/Ordered/OrderedDither.cs @@ -1,36 +1,50 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Dithering.Base; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Dithering { /// - /// Applies error diffusion based dithering using the 4x4 ordered dithering matrix. - /// + /// An ordered dithering matrix with equal sides of arbitrary length /// - public sealed class OrderedDither : OrderedDitherBase + public class OrderedDither : IOrderedDither { - /// - /// The threshold matrix. - /// This is calculated by multiplying each value in the original matrix by 16 - /// - private static readonly Fast2DArray ThresholdMatrix = - new byte[,] - { - { 0, 128, 32, 160 }, - { 192, 64, 224, 96 }, - { 48, 176, 16, 144 }, - { 240, 112, 208, 80 } - }; + private readonly Fast2DArray thresholdMatrix; + private readonly int modulusX; + private readonly int modulusY; /// /// Initializes a new instance of the class. /// - public OrderedDither() - : base(ThresholdMatrix) + /// The length of the matrix sides + public OrderedDither(uint length) + { + Fast2DArray ditherMatrix = OrderedDitherFactory.CreateDitherMatrix(length); + this.modulusX = ditherMatrix.Width; + this.modulusY = ditherMatrix.Height; + + // Adjust the matrix range for 0-255 + // It looks like it's actually possible to dither an image using it's own colors. We should investigate for V2 + // https://stackoverflow.com/questions/12422407/monochrome-dithering-in-javascript-bayer-atkinson-floyd-steinberg + int multiplier = 256 / ditherMatrix.Count; + for (int y = 0; y < ditherMatrix.Height; y++) + { + for (int x = 0; x < ditherMatrix.Width; x++) + { + ditherMatrix[y, x] = (uint)((ditherMatrix[y, x] + 1) * multiplier) - 1; + } + } + + this.thresholdMatrix = ditherMatrix; + } + + /// + public void Dither(ImageFrame image, TPixel source, TPixel upper, TPixel lower, float threshold, int x, int y) + where TPixel : struct, IPixel { + image[x, y] = this.thresholdMatrix[y % this.modulusY, x % this.modulusX] >= threshold ? lower : upper; } } } \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/OrderedDither3x3.cs b/src/ImageSharp/Dithering/Ordered/OrderedDither3x3.cs new file mode 100644 index 000000000..0436b35e9 --- /dev/null +++ b/src/ImageSharp/Dithering/Ordered/OrderedDither3x3.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Dithering +{ + /// + /// Applies order dithering using the 3x3 dithering matrix. + /// + public sealed class OrderedDither3x3 : OrderedDither + { + /// + /// Initializes a new instance of the class. + /// + public OrderedDither3x3() + : base(3) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/OrderedDitherBase.cs b/src/ImageSharp/Dithering/Ordered/OrderedDitherBase.cs deleted file mode 100644 index 818a24d5d..000000000 --- a/src/ImageSharp/Dithering/Ordered/OrderedDitherBase.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Dithering.Base -{ - /// - /// The base class for performing ordered dithering using a 4x4 matrix. - /// - public abstract class OrderedDitherBase : IOrderedDither - { - /// - /// The dithering matrix - /// - private Fast2DArray matrix; - - /// - /// Initializes a new instance of the class. - /// - /// The thresholding matrix. - internal OrderedDitherBase(Fast2DArray matrix) - { - this.matrix = matrix; - } - - /// - public void Dither(ImageFrame image, TPixel source, TPixel upper, TPixel lower, ref Rgba32 rgba, int index, int x, int y) - where TPixel : struct, IPixel - { - source.ToRgba32(ref rgba); - switch (index) - { - case 0: - image[x, y] = this.matrix[y % 3, x % 3] >= rgba.R ? lower : upper; - return; - case 1: - image[x, y] = this.matrix[y % 3, x % 3] >= rgba.G ? lower : upper; - return; - case 2: - image[x, y] = this.matrix[y % 3, x % 3] >= rgba.B ? lower : upper; - return; - case 3: - image[x, y] = this.matrix[y % 3, x % 3] >= rgba.A ? lower : upper; - return; - } - - throw new ArgumentOutOfRangeException(nameof(index), "Index should be between 0 and 3 inclusive."); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/OrderedDitherFactory.cs b/src/ImageSharp/Dithering/Ordered/OrderedDitherFactory.cs new file mode 100644 index 000000000..fc9ac2551 --- /dev/null +++ b/src/ImageSharp/Dithering/Ordered/OrderedDitherFactory.cs @@ -0,0 +1,94 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Dithering +{ + /// + /// A factory for creating ordered dither matrices. + /// + internal static class OrderedDitherFactory + { + /// + /// Creates an ordered dithering matrix with equal sides of arbitrary length. + /// + /// + /// The length of the matrix sides + /// The + public static Fast2DArray CreateDitherMatrix(uint length) + { + // Calculate the the logarithm of length to the base 2 + uint exponent = 0; + uint bayerLength = 0; + do + { + exponent++; + bayerLength = (uint)(1 << (int)exponent); + } + while (length > bayerLength); + + // Create our Bayer matrix that matches the given exponent and dimensions + var matrix = new Fast2DArray((int)length); + uint i = 0; + for (int y = 0; y < length; y++) + { + for (int x = 0; x < length; x++) + { + matrix[y, x] = Bayer(i / length, i % length, exponent); + i++; + } + } + + // If the user requested a matrix with a non-power-of-2 length e.g. 3x3 and we used 4x4 algorithm, + // we need to convert the numbers so that the resulting range is un-gapped. + // We generated: We saved: We compress the number range: + // 0 8 2 10 0 8 2 0 5 2 + // 12 4 14 6 12 4 14 7 4 8 + // 3 11 1 9 3 11 1 3 6 1 + // 15 7 13 5 + uint maxValue = bayerLength * bayerLength; + uint missing = 0; + for (uint v = 0; v < maxValue; ++v) + { + bool found = false; + for (int y = 0; y < length; ++y) + { + for (int x = 0; x < length; x++) + { + if (matrix[y, x] == v) + { + matrix[y, x] -= missing; + found = true; + break; + } + } + } + + if (!found) + { + ++missing; + } + } + + return matrix; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint Bayer(uint x, uint y, uint order) + { + uint result = 0; + for (uint i = 0; i < order; ++i) + { + uint xOdd_XOR_yOdd = (x & 1) ^ (y & 1); + uint xOdd = x & 1; + result = ((result << 1 | xOdd_XOR_yOdd) << 1) | xOdd; + x >>= 1; + y >>= 1; + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/error_diffusion.txt b/src/ImageSharp/Dithering/error_diffusion.txt new file mode 100644 index 000000000..ea412f635 --- /dev/null +++ b/src/ImageSharp/Dithering/error_diffusion.txt @@ -0,0 +1,58 @@ +List of error diffusion schemes. + +Quantization error of *current* pixel is added to the pixels +on the right and below according to the formulas below. +This works nicely for most static pictures, but causes +an avalanche of jittering artifacts if used in animation. + +Floyd-Steinberg: + + * 7 + 3 5 1 / 16 + +Jarvis-Judice-Ninke: + + * 7 5 + 3 5 7 5 3 + 1 3 5 3 1 / 48 + +Stucki: + + * 8 4 + 2 4 8 4 2 + 1 2 4 2 1 / 42 + +Burkes: + + * 8 4 + 2 4 8 4 2 / 32 + + +Sierra3: + + * 5 3 + 2 4 5 4 2 + 2 3 2 / 32 + +Sierra2: + + * 4 3 + 1 2 3 2 1 / 16 + +Sierra-2-4A: + + * 2 + 1 1 / 4 + +Stevenson-Arce: + + * . 32 + 12 . 26 . 30 . 16 + . 12 . 26 . 12 . + 5 . 12 . 12 . 5 / 200 + +Atkinson: + + * 1 1 / 8 + 1 1 1 + 1 diff --git a/src/ImageSharp/Memory/Fast2DArray{T}.cs b/src/ImageSharp/Memory/Fast2DArray{T}.cs index 14ac58baf..38ccdd279 100644 --- a/src/ImageSharp/Memory/Fast2DArray{T}.cs +++ b/src/ImageSharp/Memory/Fast2DArray{T}.cs @@ -28,6 +28,20 @@ namespace SixLabors.ImageSharp.Memory /// public int Height; + /// + /// Gets the number of items in the 2D array + /// + public int Count; + + /// + /// Initializes a new instance of the struct. + /// + /// The length of each dimension. + public Fast2DArray(int length) + : this(length, length) + { + } + /// /// Initializes a new instance of the struct. /// @@ -41,7 +55,8 @@ namespace SixLabors.ImageSharp.Memory Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); - this.Data = new T[this.Width * this.Height]; + this.Count = width * height; + this.Data = new T[this.Count]; } /// @@ -57,7 +72,8 @@ namespace SixLabors.ImageSharp.Memory Guard.MustBeGreaterThan(this.Width, 0, nameof(this.Width)); Guard.MustBeGreaterThan(this.Height, 0, nameof(this.Height)); - this.Data = new T[this.Width * this.Height]; + this.Count = this.Width * this.Height; + this.Data = new T[this.Count]; for (int y = 0; y < this.Height; y++) { @@ -96,7 +112,7 @@ namespace SixLabors.ImageSharp.Memory /// /// The source array. /// - /// The represenation on the source data. + /// The representation on the source data. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator Fast2DArray(T[,] data) diff --git a/src/ImageSharp/MetaData/ImageProperty.cs b/src/ImageSharp/MetaData/ImageProperty.cs index 62ae9d479..c60aaecfb 100644 --- a/src/ImageSharp/MetaData/ImageProperty.cs +++ b/src/ImageSharp/MetaData/ImageProperty.cs @@ -71,7 +71,12 @@ namespace SixLabors.ImageSharp.MetaData /// public static bool operator ==(ImageProperty left, ImageProperty right) { - return Equals(left, right); + if (ReferenceEquals(left, right)) + { + return true; + } + + return left.Equals(right); } /// @@ -90,7 +95,7 @@ namespace SixLabors.ImageSharp.MetaData /// public static bool operator !=(ImageProperty left, ImageProperty right) { - return !Equals(left, right); + return !(left == right); } /// diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs index 64508137b..7ffe9d48f 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs @@ -188,7 +188,12 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// public static bool operator ==(ExifValue left, ExifValue right) { - return ExifValue.Equals(left, right); + if (ReferenceEquals(left, right)) + { + return true; + } + + return left.Equals(right); } /// @@ -205,7 +210,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// public static bool operator !=(ExifValue left, ExifValue right) { - return !ExifValue.Equals(left, right); + return !(left == right); } /// diff --git a/src/ImageSharp/MetaData/Profiles/Exif/Rational.cs b/src/ImageSharp/MetaData/Profiles/Exif/Rational.cs index 6d62a623f..0f47870d2 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/Rational.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/Rational.cs @@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// The public static bool operator ==(Rational left, Rational right) { - return Rational.Equals(left, right); + return left.Equals(right); } /// @@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// The public static bool operator !=(Rational left, Rational right) { - return !Rational.Equals(left, right); + return !left.Equals(right); } /// diff --git a/src/ImageSharp/MetaData/Profiles/Exif/SignedRational.cs b/src/ImageSharp/MetaData/Profiles/Exif/SignedRational.cs index f2fe35924..17f1b568b 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/SignedRational.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/SignedRational.cs @@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// The public static bool operator ==(SignedRational left, SignedRational right) { - return SignedRational.Equals(left, right); + return left.Equals(right); } /// @@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// The public static bool operator !=(SignedRational left, SignedRational right) { - return !SignedRational.Equals(left, right); + return !left.Equals(right); } /// diff --git a/src/ImageSharp/PixelFormats/ColorConstants.cs b/src/ImageSharp/PixelFormats/ColorConstants.cs index f97d3b190..bac05c53d 100644 --- a/src/ImageSharp/PixelFormats/ColorConstants.cs +++ b/src/ImageSharp/PixelFormats/ColorConstants.cs @@ -1,9 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Collections.Generic; - namespace SixLabors.ImageSharp.PixelFormats { /// @@ -11,23 +8,17 @@ namespace SixLabors.ImageSharp.PixelFormats /// public static class ColorConstants { - /// - /// Provides a lazy, one time method of returning the colors. - /// - private static readonly Lazy SafeColors = new Lazy(GetWebSafeColors); - /// /// Gets a collection of named, web safe, colors as defined in the CSS Color Module Level 4. /// - public static Rgba32[] WebSafeColors => SafeColors.Value; + public static readonly Rgba32[] WebSafeColors = GetWebSafeColors(); /// /// Returns an array of web safe colors. /// /// The private static Rgba32[] GetWebSafeColors() - { - return new List + => new Rgba32[] { Rgba32.AliceBlue, Rgba32.AntiqueWhite, @@ -171,7 +162,6 @@ namespace SixLabors.ImageSharp.PixelFormats Rgba32.WhiteSmoke, Rgba32.Yellow, Rgba32.YellowGreen - }.ToArray(); - } + }; } -} +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/NamedColors{TPixel}.cs b/src/ImageSharp/PixelFormats/NamedColors{TPixel}.cs index 45050de72..6a2902f9b 100644 --- a/src/ImageSharp/PixelFormats/NamedColors{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/NamedColors{TPixel}.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; + namespace SixLabors.ImageSharp.PixelFormats { /// @@ -10,6 +12,11 @@ namespace SixLabors.ImageSharp.PixelFormats public static class NamedColors where TPixel : struct, IPixel { + /// + /// Thread-safe backing field for . + /// + private static readonly Lazy WebSafePaletteLazy = new Lazy(GetWebSafePalette, true); + /// /// Represents a matching the W3C definition that has an hex value of #F0F8FF. /// @@ -719,5 +726,20 @@ namespace SixLabors.ImageSharp.PixelFormats /// Represents a matching the W3C definition that has an hex value of #9ACD32. /// public static readonly TPixel YellowGreen = ColorBuilder.FromRGBA(154, 205, 50, 255); + + /// + /// Gets a matching the W3C definition of web safe colors. + /// + public static TPixel[] WebSafePalette => WebSafePaletteLazy.Value; + + private static TPixel[] GetWebSafePalette() + { + Rgba32[] constants = ColorConstants.WebSafeColors; + TPixel[] safe = new TPixel[constants.Length + 1]; + + Span constantsBytes = constants.AsSpan().NonPortableCast(); + PixelOperations.Instance.PackFromRgba32Bytes(constantsBytes, safe, constants.Length); + return safe; + } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Binarization/BinaryDiffuse.cs b/src/ImageSharp/Processing/Binarization/BinaryDiffuse.cs new file mode 100644 index 000000000..eb5008757 --- /dev/null +++ b/src/ImageSharp/Processing/Binarization/BinaryDiffuse.cs @@ -0,0 +1,86 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Dithering; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Dithers the image reducing it to two colors using error diffusion. + /// + /// The pixel format. + /// The image this method extends. + /// The diffusion algorithm to apply. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The . + public static IImageProcessingContext BinaryDiffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new BinaryErrorDiffusionProcessor(diffuser, threshold)); + return source; + } + + /// + /// Dithers the image reducing it to two colors using error diffusion. + /// + /// The pixel format. + /// The image this method extends. + /// The diffusion algorithm to apply. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext BinaryDiffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold, Rectangle rectangle) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new BinaryErrorDiffusionProcessor(diffuser, threshold), rectangle); + return source; + } + + /// + /// Dithers the image reducing it to two colors using error diffusion. + /// + /// The pixel format. + /// The image this method extends. + /// The diffusion algorithm to apply. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold + /// The . + public static IImageProcessingContext BinaryDiffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold, TPixel upperColor, TPixel lowerColor) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new BinaryErrorDiffusionProcessor(diffuser, threshold, upperColor, lowerColor)); + return source; + } + + /// + /// Dithers the image reducing it to two colors using error diffusion. + /// + /// The pixel format. + /// The image this method extends. + /// The diffusion algorithm to apply. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext BinaryDiffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold, TPixel upperColor, TPixel lowerColor, Rectangle rectangle) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new BinaryErrorDiffusionProcessor(diffuser, threshold, upperColor, lowerColor), rectangle); + return source; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Binarization/BinaryDither.cs b/src/ImageSharp/Processing/Binarization/BinaryDither.cs new file mode 100644 index 000000000..715dff472 --- /dev/null +++ b/src/ImageSharp/Processing/Binarization/BinaryDither.cs @@ -0,0 +1,82 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Dithering; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Dithers the image reducing it to two colors using ordered dithering. + /// + /// The pixel format. + /// The image this method extends. + /// The ordered ditherer. + /// The . + public static IImageProcessingContext BinaryDither(this IImageProcessingContext source, IOrderedDither dither) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new BinaryOrderedDitherProcessor(dither)); + return source; + } + + /// + /// Dithers the image reducing it to two colors using ordered dithering. + /// + /// The pixel format. + /// The image this method extends. + /// The ordered ditherer. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold + /// The . + public static IImageProcessingContext BinaryDither(this IImageProcessingContext source, IOrderedDither dither, TPixel upperColor, TPixel lowerColor) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new BinaryOrderedDitherProcessor(dither, upperColor, lowerColor)); + return source; + } + + /// + /// Dithers the image reducing it to two colors using ordered dithering. + /// + /// The pixel format. + /// The image this method extends. + /// The ordered ditherer. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext BinaryDither(this IImageProcessingContext source, IOrderedDither dither, Rectangle rectangle) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new BinaryOrderedDitherProcessor(dither), rectangle); + return source; + } + + /// + /// Dithers the image reducing it to two colors using ordered dithering. + /// + /// The pixel format. + /// The image this method extends. + /// The ordered ditherer. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext BinaryDither(this IImageProcessingContext source, IOrderedDither dither, TPixel upperColor, TPixel lowerColor, Rectangle rectangle) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new BinaryOrderedDitherProcessor(dither, upperColor, lowerColor), rectangle); + return source; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Binarization/BinaryThreshold.cs b/src/ImageSharp/Processing/Binarization/BinaryThreshold.cs index 5a165659b..3f86528f5 100644 --- a/src/ImageSharp/Processing/Binarization/BinaryThreshold.cs +++ b/src/ImageSharp/Processing/Binarization/BinaryThreshold.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors; using SixLabors.Primitives; @@ -43,5 +42,40 @@ namespace SixLabors.ImageSharp source.ApplyProcessor(new BinaryThresholdProcessor(threshold), rectangle); return source; } + + /// + /// Applies binarization to the image splitting the pixels at the given threshold. + /// + /// The pixel format. + /// The image this method extends. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold + /// The . + public static IImageProcessingContext BinaryThreshold(this IImageProcessingContext source, float threshold, TPixel upperColor, TPixel lowerColor) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor)); + return source; + } + + /// + /// Applies binarization to the image splitting the pixels at the given threshold. + /// + /// The pixel format. + /// The image this method extends. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext BinaryThreshold(this IImageProcessingContext source, float threshold, TPixel upperColor, TPixel lowerColor, Rectangle rectangle) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor), rectangle); + return source; + } } } diff --git a/src/ImageSharp/Processing/Dithering/Diffuse.cs b/src/ImageSharp/Processing/Dithering/Diffuse.cs new file mode 100644 index 000000000..e6b82d313 --- /dev/null +++ b/src/ImageSharp/Processing/Dithering/Diffuse.cs @@ -0,0 +1,84 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Dithering; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Dithers the image reducing it to a web-safe palette using error diffusion. + /// + /// The pixel format. + /// The image this method extends. + /// The diffusion algorithm to apply. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The . + public static IImageProcessingContext Diffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold)); + return source; + } + + /// + /// Dithers the image reducing it to a web-safe palette using error diffusion. + /// + /// The pixel format. + /// The image this method extends. + /// The diffusion algorithm to apply. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Diffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold, Rectangle rectangle) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold), rectangle); + return source; + } + + /// + /// Dithers the image reducing it to the given palette using error diffusion. + /// + /// The pixel format. + /// The image this method extends. + /// The diffusion algorithm to apply. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The palette to select substitute colors from. + /// The . + public static IImageProcessingContext Diffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold, TPixel[] palette) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold, palette)); + return source; + } + + /// + /// Dithers the image reducing it to the given palette using error diffusion. + /// + /// The pixel format. + /// The image this method extends. + /// The diffusion algorithm to apply. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The palette to select substitute colors from. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Diffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold, TPixel[] palette, Rectangle rectangle) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold, palette), rectangle); + return source; + } + } +} diff --git a/src/ImageSharp/Processing/Binarization/Dither.cs b/src/ImageSharp/Processing/Dithering/Dither.cs similarity index 50% rename from src/ImageSharp/Processing/Binarization/Dither.cs rename to src/ImageSharp/Processing/Dithering/Dither.cs index f21ccf0bd..85fdef24b 100644 --- a/src/ImageSharp/Processing/Binarization/Dither.cs +++ b/src/ImageSharp/Processing/Dithering/Dither.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using SixLabors.ImageSharp.Dithering; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors; @@ -15,7 +14,7 @@ namespace SixLabors.ImageSharp public static partial class ImageExtensions { /// - /// Dithers the image reducing it to two colors using ordered dithering. + /// Dithers the image reducing it to a web-safe palette using ordered dithering. /// /// The pixel format. /// The image this method extends. @@ -24,27 +23,27 @@ namespace SixLabors.ImageSharp public static IImageProcessingContext Dither(this IImageProcessingContext source, IOrderedDither dither) where TPixel : struct, IPixel { - source.ApplyProcessor(new OrderedDitherProcessor(dither, 0)); + source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither)); return source; } /// - /// Dithers the image reducing it to two colors using ordered dithering. + /// Dithers the image reducing it to the given palette using ordered dithering. /// /// The pixel format. /// The image this method extends. /// The ordered ditherer. - /// The component index to test the threshold against. Must range from 0 to 3. + /// The palette to select substitute colors from. /// The . - public static IImageProcessingContext Dither(this IImageProcessingContext source, IOrderedDither dither, int index) + public static IImageProcessingContext Dither(this IImageProcessingContext source, IOrderedDither dither, TPixel[] palette) where TPixel : struct, IPixel { - source.ApplyProcessor(new OrderedDitherProcessor(dither, index)); + source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither, palette)); return source; } /// - /// Dithers the image reducing it to two colors using ordered dithering. + /// Dithers the image reducing it to a web-safe palette using ordered dithering. /// /// The pixel format. /// The image this method extends. @@ -56,58 +55,25 @@ namespace SixLabors.ImageSharp public static IImageProcessingContext Dither(this IImageProcessingContext source, IOrderedDither dither, Rectangle rectangle) where TPixel : struct, IPixel { - source.ApplyProcessor(new OrderedDitherProcessor(dither, 0), rectangle); + source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither), rectangle); return source; } /// - /// Dithers the image reducing it to two colors using ordered dithering. + /// Dithers the image reducing it to the given palette using ordered dithering. /// /// The pixel format. /// The image this method extends. /// The ordered ditherer. + /// The palette to select substitute colors from. /// /// The structure that specifies the portion of the image object to alter. /// - /// The component index to test the threshold against. Must range from 0 to 3. /// The . - public static IImageProcessingContext Dither(this IImageProcessingContext source, IOrderedDither dither, Rectangle rectangle, int index) + public static IImageProcessingContext Dither(this IImageProcessingContext source, IOrderedDither dither, TPixel[] palette, Rectangle rectangle) where TPixel : struct, IPixel { - source.ApplyProcessor(new OrderedDitherProcessor(dither, index), rectangle); - return source; - } - - /// - /// Dithers the image reducing it to two colors using error diffusion. - /// - /// The pixel format. - /// The image this method extends. - /// The diffusion algorithm to apply. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// The . - public static IImageProcessingContext Dither(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold) - where TPixel : struct, IPixel - { - source.ApplyProcessor(new ErrorDiffusionDitherProcessor(diffuser, threshold)); - return source; - } - - /// - /// Dithers the image reducing it to two colors using error diffusion. - /// - /// The pixel format. - /// The image this method extends. - /// The diffusion algorithm to apply. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Dither(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold, Rectangle rectangle) - where TPixel : struct, IPixel - { - source.ApplyProcessor(new ErrorDiffusionDitherProcessor(diffuser, threshold), rectangle); + source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither, palette), rectangle); return source; } } diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs new file mode 100644 index 000000000..80a423645 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs @@ -0,0 +1,123 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Dithering; +using SixLabors.ImageSharp.Helpers; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors +{ + /// + /// Performs binary threshold filtering against an image using error diffusion. + /// + /// The pixel format. + internal class BinaryErrorDiffusionProcessor : ImageProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The error diffuser + public BinaryErrorDiffusionProcessor(IErrorDiffuser diffuser) + : this(diffuser, .5F) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error diffuser + /// The threshold to split the image. Must be between 0 and 1. + public BinaryErrorDiffusionProcessor(IErrorDiffuser diffuser, float threshold) + : this(diffuser, threshold, NamedColors.White, NamedColors.Black) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error diffuser + /// The threshold to split the image. Must be between 0 and 1. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold. + public BinaryErrorDiffusionProcessor(IErrorDiffuser diffuser, float threshold, TPixel upperColor, TPixel lowerColor) + { + Guard.NotNull(diffuser, nameof(diffuser)); + Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); + + this.Diffuser = diffuser; + this.Threshold = threshold; + this.UpperColor = upperColor; + this.LowerColor = lowerColor; + } + + /// + /// Gets the error diffuser. + /// + public IErrorDiffuser Diffuser { get; } + + /// + /// Gets the threshold value. + /// + public float Threshold { get; } + + /// + /// Gets the color to use for pixels that are above the threshold. + /// + public TPixel UpperColor { get; } + + /// + /// Gets the color to use for pixels that fall below the threshold. + /// + public TPixel LowerColor { get; } + + /// + protected override void OnApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + float threshold = this.Threshold * 255F; + var rgba = default(Rgba32); + bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); + + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + int startY = interest.Y; + int endY = interest.Bottom; + int startX = interest.X; + int endX = interest.Right; + + // Collect the values before looping so we can reduce our calculation count for identical sibling pixels + TPixel sourcePixel = source[startX, startY]; + TPixel previousPixel = sourcePixel; + sourcePixel.ToRgba32(ref rgba); + + // Convert to grayscale using ITU-R Recommendation BT.709 if required + float luminance = isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B); + + for (int y = startY; y < endY; y++) + { + Span row = source.GetPixelRowSpan(y); + + for (int x = startX; x < endX; x++) + { + sourcePixel = row[x]; + + // Check if this is the same as the last pixel. If so use that value + // rather than calculating it again. This is an inexpensive optimization. + if (!previousPixel.Equals(sourcePixel)) + { + sourcePixel.ToRgba32(ref rgba); + luminance = isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B); + + // Setup the previous pointer + previousPixel = sourcePixel; + } + + TPixel transformedPixel = luminance >= threshold ? this.UpperColor : this.LowerColor; + this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, startY, endX, endY); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor.cs new file mode 100644 index 000000000..baa8df8cc --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor.cs @@ -0,0 +1,103 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Dithering; +using SixLabors.ImageSharp.Helpers; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors +{ + /// + /// Performs binary threshold filtering against an image using ordered dithering. + /// + /// The pixel format. + internal class BinaryOrderedDitherProcessor : ImageProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The ordered ditherer. + public BinaryOrderedDitherProcessor(IOrderedDither dither) + : this(dither, NamedColors.White, NamedColors.Black) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The ordered ditherer. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold. + public BinaryOrderedDitherProcessor(IOrderedDither dither, TPixel upperColor, TPixel lowerColor) + { + Guard.NotNull(dither, nameof(dither)); + + this.Dither = dither; + this.UpperColor = upperColor; + this.LowerColor = lowerColor; + } + + /// + /// Gets the ditherer. + /// + public IOrderedDither Dither { get; } + + /// + /// Gets the color to use for pixels that are above the threshold. + /// + public TPixel UpperColor { get; } + + /// + /// Gets the color to use for pixels that fall below the threshold. + /// + public TPixel LowerColor { get; } + + /// + protected override void OnApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + var rgba = default(Rgba32); + bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); + + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + int startY = interest.Y; + int endY = interest.Bottom; + int startX = interest.X; + int endX = interest.Right; + + // Collect the values before looping so we can reduce our calculation count for identical sibling pixels + TPixel sourcePixel = source[startX, startY]; + TPixel previousPixel = sourcePixel; + sourcePixel.ToRgba32(ref rgba); + + // Convert to grayscale using ITU-R Recommendation BT.709 if required + float luminance = isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B); + + for (int y = startY; y < endY; y++) + { + Span row = source.GetPixelRowSpan(y); + + for (int x = startX; x < endX; x++) + { + sourcePixel = row[x]; + + // Check if this is the same as the last pixel. If so use that value + // rather than calculating it again. This is an inexpensive optimization. + if (!previousPixel.Equals(sourcePixel)) + { + sourcePixel.ToRgba32(ref rgba); + luminance = isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B); + + // Setup the previous pointer + previousPixel = sourcePixel; + } + + this.Dither.Dither(source, sourcePixel, this.UpperColor, this.LowerColor, luminance, x, y); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs index 434ed0269..609b09092 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs @@ -4,14 +4,14 @@ using System; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Helpers; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors { /// - /// An to perform binary threshold filtering against an - /// . The image will be converted to grayscale before thresholding occurs. + /// Performs simple binary threshold filtering against an image. /// /// The pixel format. internal class BinaryThresholdProcessor : ImageProcessor @@ -22,14 +22,22 @@ namespace SixLabors.ImageSharp.Processing.Processors /// /// The threshold to split the image. Must be between 0 and 1. public BinaryThresholdProcessor(float threshold) + : this(threshold, NamedColors.White, NamedColors.Black) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The threshold to split the image. Must be between 0 and 1. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold. + public BinaryThresholdProcessor(float threshold, TPixel upperColor, TPixel lowerColor) { - // TODO: Check thresholding limit. Colors should probably have Max/Min/Middle properties. Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); this.Threshold = threshold; - - // Default to white/black for upper/lower. - this.UpperColor = NamedColors.White; - this.LowerColor = NamedColors.Black; + this.UpperColor = upperColor; + this.LowerColor = lowerColor; } /// @@ -47,55 +55,38 @@ namespace SixLabors.ImageSharp.Processing.Processors /// public TPixel LowerColor { get; set; } - /// - protected override void BeforeApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) - { - new GrayscaleBt709Processor(1F).Apply(source, sourceRectangle, configuration); - } - /// protected override void OnApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) { - float threshold = this.Threshold; + float threshold = this.Threshold * 255F; TPixel upper = this.UpperColor; TPixel lower = this.LowerColor; - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - - // Align start/end positions. - int minX = Math.Max(0, startX); - int maxX = Math.Min(source.Width, endX); - int minY = Math.Max(0, startY); - int maxY = Math.Min(source.Height, endY); - - // Reset offset if necessary. - if (minX > 0) - { - startX = 0; - } + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + int startY = interest.Y; + int endY = interest.Bottom; + int startX = interest.X; + int endX = interest.Right; - if (minY > 0) - { - startY = 0; - } + bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); Parallel.For( - minY, - maxY, + startY, + endY, configuration.ParallelOptions, y => { - Span row = source.GetPixelRowSpan(y - startY); + Span row = source.GetPixelRowSpan(y); + var rgba = default(Rgba32); - for (int x = minX; x < maxX; x++) + for (int x = startX; x < endX; x++) { - ref TPixel color = ref row[x - startX]; + ref TPixel color = ref row[x]; + color.ToRgba32(ref rgba); - // Any channel will do since it's Grayscale. - color = color.ToVector4().X >= threshold ? upper : lower; + // Convert to grayscale using ITU-R Recommendation BT.709 if required + float luminance = isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B); + color = luminance >= threshold ? upper : lower; } }); } diff --git a/src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs deleted file mode 100644 index 01cba15c4..000000000 --- a/src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Dithering; -using SixLabors.ImageSharp.Helpers; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors -{ - /// - /// An that dithers an image using error diffusion. - /// - /// The pixel format. - internal class ErrorDiffusionDitherProcessor : ImageProcessor - where TPixel : struct, IPixel - { - /// - /// Initializes a new instance of the class. - /// - /// The error diffuser - /// The threshold to split the image. Must be between 0 and 1. - public ErrorDiffusionDitherProcessor(IErrorDiffuser diffuser, float threshold) - { - Guard.NotNull(diffuser, nameof(diffuser)); - - this.Diffuser = diffuser; - this.Threshold = threshold; - - // Default to white/black for upper/lower. - this.UpperColor = NamedColors.White; - this.LowerColor = NamedColors.Black; - } - - /// - /// Gets the error diffuser. - /// - public IErrorDiffuser Diffuser { get; } - - /// - /// Gets the threshold value. - /// - public float Threshold { get; } - - /// - /// Gets or sets the color to use for pixels that are above the threshold. - /// - public TPixel UpperColor { get; set; } - - /// - /// Gets or sets the color to use for pixels that fall below the threshold. - /// - public TPixel LowerColor { get; set; } - - /// - protected override void BeforeApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) - { - new GrayscaleBt709Processor(1F).Apply(source, sourceRectangle, configuration); - } - - /// - protected override void OnApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) - { - var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); - int startY = interest.Y; - int endY = interest.Bottom; - int startX = interest.X; - int endX = interest.Right; - - for (int y = startY; y < endY; y++) - { - Span row = source.GetPixelRowSpan(y); - - for (int x = startX; x < endX; x++) - { - TPixel sourceColor = row[x]; - TPixel transformedColor = sourceColor.ToVector4().X >= this.Threshold ? this.UpperColor : this.LowerColor; - this.Diffuser.Dither(source, sourceColor, transformedColor, x, y, startX, startY, endX, endY); - } - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs deleted file mode 100644 index a37d12f18..000000000 --- a/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Dithering; -using SixLabors.ImageSharp.Helpers; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors -{ - /// - /// An that dithers an image using error diffusion. - /// - /// The pixel format. - internal class OrderedDitherProcessor : ImageProcessor - where TPixel : struct, IPixel - { - /// - /// Initializes a new instance of the class. - /// - /// The ordered ditherer. - /// The component index to test the threshold against. Must range from 0 to 3. - public OrderedDitherProcessor(IOrderedDither dither, int index) - { - Guard.NotNull(dither, nameof(dither)); - Guard.MustBeBetweenOrEqualTo(index, 0, 3, nameof(index)); - - // Alpha8 only stores the pixel data in the alpha channel. - if (typeof(TPixel) == typeof(Alpha8)) - { - index = 3; - } - - this.Dither = dither; - this.Index = index; - - // Default to white/black for upper/lower. - this.UpperColor = NamedColors.White; - this.LowerColor = NamedColors.Black; - } - - /// - /// Gets the ditherer. - /// - public IOrderedDither Dither { get; } - - /// - /// Gets the component index to test the threshold against. - /// - public int Index { get; } - - /// - /// Gets or sets the color to use for pixels that are above the threshold. - /// - public TPixel UpperColor { get; set; } - - /// - /// Gets or sets the color to use for pixels that fall below the threshold. - /// - public TPixel LowerColor { get; set; } - - /// - protected override void BeforeApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) - { - new GrayscaleBt709Processor(1F).Apply(source, sourceRectangle, configuration); - } - - /// - protected override void OnApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) - { - var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); - int startY = interest.Y; - int endY = interest.Bottom; - int startX = interest.X; - int endX = interest.Right; - - var rgba = default(Rgba32); - for (int y = startY; y < endY; y++) - { - Span row = source.GetPixelRowSpan(y); - - for (int x = startX; x < endX; x++) - { - TPixel sourceColor = row[x]; - this.Dither.Dither(source, sourceColor, this.UpperColor, this.LowerColor, ref rgba, this.Index, x, y); - } - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs new file mode 100644 index 000000000..152959cb7 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs @@ -0,0 +1,113 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Dithering; +using SixLabors.ImageSharp.Helpers; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors +{ + /// + /// An that dithers an image using error diffusion. + /// + /// The pixel format. + internal class ErrorDiffusionPaletteProcessor : PaletteDitherProcessorBase + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The error diffuser + public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser) + : this(diffuser, .5F) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error diffuser + /// The threshold to split the image. Must be between 0 and 1. + public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser, float threshold) + : this(diffuser, threshold, NamedColors.WebSafePalette) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error diffuser + /// The threshold to split the image. Must be between 0 and 1. + /// The palette to select substitute colors from. + public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser, float threshold, TPixel[] palette) + : base(palette) + { + Guard.NotNull(diffuser, nameof(diffuser)); + Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); + + this.Diffuser = diffuser; + this.Threshold = threshold; + } + + /// + /// Gets the error diffuser. + /// + public IErrorDiffuser Diffuser { get; } + + /// + /// Gets the threshold value. + /// + public float Threshold { get; } + + /// + protected override void OnApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + float threshold = this.Threshold * 255F; + var rgba = default(Rgba32); + bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); + + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + int startY = interest.Y; + int endY = interest.Bottom; + int startX = interest.X; + int endX = interest.Right; + + // Collect the values before looping so we can reduce our calculation count for identical sibling pixels + TPixel sourcePixel = source[startX, startY]; + TPixel previousPixel = sourcePixel; + PixelPair pair = this.GetClosestPixelPair(ref sourcePixel, this.Palette); + sourcePixel.ToRgba32(ref rgba); + + // Convert to grayscale using ITU-R Recommendation BT.709 if required + float luminance = isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B); + + for (int y = startY; y < endY; y++) + { + Span row = source.GetPixelRowSpan(y); + + for (int x = startX; x < endX; x++) + { + sourcePixel = row[x]; + + // Check if this is the same as the last pixel. If so use that value + // rather than calculating it again. This is an inexpensive optimization. + if (!previousPixel.Equals(sourcePixel)) + { + pair = this.GetClosestPixelPair(ref sourcePixel, this.Palette); + sourcePixel.ToRgba32(ref rgba); + luminance = isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B); + + // Setup the previous pointer + previousPixel = sourcePixel; + } + + TPixel transformedPixel = luminance >= threshold ? pair.Second : pair.First; + this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, startY, endX, endY); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs new file mode 100644 index 000000000..4fc59585a --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs @@ -0,0 +1,93 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Dithering; +using SixLabors.ImageSharp.Helpers; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors +{ + /// + /// An that dithers an image using error diffusion. + /// If no palette is given this will default to the web safe colors defined in the CSS Color Module Level 4. + /// + /// The pixel format. + internal class OrderedDitherPaletteProcessor : PaletteDitherProcessorBase + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The ordered ditherer. + public OrderedDitherPaletteProcessor(IOrderedDither dither) + : this(dither, NamedColors.WebSafePalette) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The ordered ditherer. + /// The palette to select substitute colors from. + public OrderedDitherPaletteProcessor(IOrderedDither dither, TPixel[] palette) + : base(palette) + { + Guard.NotNull(dither, nameof(dither)); + this.Dither = dither; + } + + /// + /// Gets the ditherer. + /// + public IOrderedDither Dither { get; } + + /// + protected override void OnApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + var rgba = default(Rgba32); + bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); + + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + int startY = interest.Y; + int endY = interest.Bottom; + int startX = interest.X; + int endX = interest.Right; + + // Collect the values before looping so we can reduce our calculation count for identical sibling pixels + TPixel sourcePixel = source[startX, startY]; + TPixel previousPixel = sourcePixel; + PixelPair pair = this.GetClosestPixelPair(ref sourcePixel, this.Palette); + sourcePixel.ToRgba32(ref rgba); + + // Convert to grayscale using ITU-R Recommendation BT.709 if required + float luminance = isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B); + + for (int y = startY; y < endY; y++) + { + Span row = source.GetPixelRowSpan(y); + + for (int x = startX; x < endX; x++) + { + sourcePixel = row[x]; + + // Check if this is the same as the last pixel. If so use that value + // rather than calculating it again. This is an inexpensive optimization. + if (!previousPixel.Equals(sourcePixel)) + { + pair = this.GetClosestPixelPair(ref sourcePixel, this.Palette); + sourcePixel.ToRgba32(ref rgba); + luminance = isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B); + + // Setup the previous pointer + previousPixel = sourcePixel; + } + + this.Dither.Dither(source, sourcePixel, pair.Second, pair.First, luminance, x, y); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessorBase.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessorBase.cs new file mode 100644 index 000000000..4e6b7bec0 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessorBase.cs @@ -0,0 +1,75 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors +{ + /// + /// The base class for dither and diffusion processors that consume a palette. + /// + internal abstract class PaletteDitherProcessorBase : ImageProcessor + where TPixel : struct, IPixel + { + private readonly Dictionary> cache = new Dictionary>(); + + /// + /// Initializes a new instance of the class. + /// + /// The palette to select substitute colors from. + public PaletteDitherProcessorBase(TPixel[] palette) + { + Guard.NotNull(palette, nameof(palette)); + this.Palette = palette; + } + + /// + /// Gets the palette to select substitute colors from. + /// + public TPixel[] Palette { get; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected PixelPair GetClosestPixelPair(ref TPixel pixel, TPixel[] colorPalette) + { + // Check if the color is in the lookup table + if (this.cache.ContainsKey(pixel)) + { + return this.cache[pixel]; + } + + // Not found - loop through the palette and find the nearest match. + float leastDistance = int.MaxValue; + float secondLeastDistance = int.MaxValue; + var vector = pixel.ToVector4(); + + var closest = default(TPixel); + var secondClosest = default(TPixel); + for (int index = 0; index < colorPalette.Length; index++) + { + TPixel temp = colorPalette[index]; + float distance = Vector4.DistanceSquared(vector, temp.ToVector4()); + + if (distance < leastDistance) + { + leastDistance = distance; + secondClosest = closest; + closest = temp; + } + else if (distance < secondLeastDistance) + { + secondLeastDistance = distance; + secondClosest = temp; + } + } + + // Pop it into the cache for next time + var pair = new PixelPair(closest, secondClosest); + this.cache.Add(pixel, pair); + + return pair; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/PixelPair.cs b/src/ImageSharp/Processing/Processors/Dithering/PixelPair.cs new file mode 100644 index 000000000..e3b9c11bd --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/PixelPair.cs @@ -0,0 +1,49 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors +{ + /// + /// Represents a composite pair of pixels. Used for caching color distance lookups. + /// + /// The pixel format. + internal struct PixelPair : IEquatable> + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the struct. + /// + /// The first pixel color + /// The second pixel color + public PixelPair(TPixel first, TPixel second) + { + this.First = first; + this.Second = second; + } + + /// + /// Gets the first pixel color + /// + public TPixel First { get; } + + /// + /// Gets the second pixel color + /// + public TPixel Second { get; } + + /// + public bool Equals(PixelPair other) + => this.First.Equals(other.First) && this.Second.Equals(other.Second); + + /// + public override bool Equals(object obj) + => obj is PixelPair other && this.First.Equals(other.First) && this.Second.Equals(other.Second); + + /// + public override int GetHashCode() + => HashHelpers.Combine(this.First.GetHashCode(), this.Second.GetHashCode()); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs b/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs index 2d97f6584..fcd7b2e8f 100644 --- a/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs @@ -6,7 +6,7 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors { /// - /// Applies a greyscale filter matrix using the given amount and the formula as specified by ITU-R Recommendation BT.709 + /// Applies a grayscale filter matrix using the given amount and the formula as specified by ITU-R Recommendation BT.709 /// /// The pixel format. internal class GrayscaleBt709Processor : FilterProcessor diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs index 0d8d0d911..b05d77868 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs @@ -191,25 +191,5 @@ namespace SixLabors.ImageSharp.Processing.Processors }); } } - - /// - protected override void AfterImageApply(Image source, Image destination, Rectangle sourceRectangle) - { - ExifProfile profile = destination.MetaData.ExifProfile; - if (profile == null) - { - return; - } - - if (profile.GetValue(ExifTag.PixelXDimension) != null) - { - profile.SetValue(ExifTag.PixelXDimension, destination.Width); - } - - if (profile.GetValue(ExifTag.PixelYDimension) != null) - { - profile.SetValue(ExifTag.PixelYDimension, destination.Height); - } - } } } \ No newline at end of file diff --git a/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs index d646a680e..8b8db6177 100644 --- a/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs @@ -107,7 +107,7 @@ namespace SixLabors.ImageSharp.Quantizers if (this.Dither) { // Apply the dithering matrix. We have to reapply the value now as the original has changed. - this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, 0, 0, width, height, false); + this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, 0, 0, width, height); } output[(y * source.Width) + x] = pixelValue; diff --git a/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs index 0b95c09a6..cd1b4b07b 100644 --- a/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs @@ -12,6 +12,7 @@ namespace SixLabors.ImageSharp.Quantizers { /// /// Encapsulates methods to create a quantized image based upon the given palette. + /// If no palette is given this will default to the web safe colors defined in the CSS Color Module Level 4. /// /// /// The pixel format. @@ -31,27 +32,20 @@ namespace SixLabors.ImageSharp.Quantizers /// /// Initializes a new instance of the class. /// - /// - /// The color palette. If none is given this will default to the web safe colors defined - /// in the CSS Color Module Level 4. - /// + public PaletteQuantizer() + : this(NamedColors.WebSafePalette) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The palette to select substitute colors from. public PaletteQuantizer(TPixel[] palette = null) : base(true) { - if (palette == null) - { - Rgba32[] constants = ColorConstants.WebSafeColors; - TPixel[] safe = new TPixel[constants.Length + 1]; - - Span constantsBytes = constants.AsSpan().NonPortableCast(); - - PixelOperations.Instance.PackFromRgba32Bytes(constantsBytes, safe, constants.Length); - this.colors = safe; - } - else - { - this.colors = palette; - } + Guard.NotNull(palette, nameof(palette)); + this.colors = palette; } /// @@ -102,7 +96,7 @@ namespace SixLabors.ImageSharp.Quantizers if (this.Dither) { // Apply the dithering matrix. We have to reapply the value now as the original has changed. - this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, 0, 0, width, height, false); + this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, 0, 0, width, height); } output[(y * source.Width) + x] = pixelValue; diff --git a/src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs b/src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs index 20ba2e637..31e424060 100644 --- a/src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs +++ b/src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs @@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Quantizers.Base public bool Dither { get; set; } = true; /// - public IErrorDiffuser DitherType { get; set; } = new FloydSteinbergDiffuser(); + public IErrorDiffuser DitherType { get; set; } = KnownDiffusers.FloydSteinberg; /// public virtual QuantizedImage Quantize(ImageFrame image, int maxColors) diff --git a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs index f08114574..ce2a71da4 100644 --- a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs @@ -272,7 +272,7 @@ namespace SixLabors.ImageSharp.Quantizers if (this.Dither) { // Apply the dithering matrix. We have to reapply the value now as the original has changed. - this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, 0, 0, width, height, false); + this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, 0, 0, width, height); } output[(y * source.Width) + x] = pixelValue; diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index bf546c91b..4e1776816 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -1,6 +1,6 @@  - netcoreapp1.1;net461 + netcoreapp2.0;net461 Exe True SixLabors.ImageSharp.Benchmarks @@ -17,15 +17,12 @@ + + + - - - - - - - + diff --git a/tests/ImageSharp.Benchmarks/benchmark.sh b/tests/ImageSharp.Benchmarks/benchmark.sh index 1966475bc..f51a9833a 100755 --- a/tests/ImageSharp.Benchmarks/benchmark.sh +++ b/tests/ImageSharp.Benchmarks/benchmark.sh @@ -1,7 +1,7 @@ #!/bin/bash # Build in release mode -dotnet build -c Release -f netcoreapp1.1 +dotnet build -c Release -f netcoreapp2.0 # Run benchmarks -dotnet bin/Release/netcoreapp1.1/ImageSharp.Benchmarks.dll \ No newline at end of file +dotnet bin/Release/netcoreapp2.0/ImageSharp.Benchmarks.dll \ No newline at end of file diff --git a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj index b186ff4df..7d56686eb 100644 --- a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj +++ b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj @@ -18,8 +18,8 @@ - - + + diff --git a/tests/ImageSharp.Tests/Colorspaces/ColorConverterAdaptTest.cs b/tests/ImageSharp.Tests/Colorspaces/ColorConverterAdaptTest.cs index f25ef92ff..3d332eaf2 100644 --- a/tests/ImageSharp.Tests/Colorspaces/ColorConverterAdaptTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/ColorConverterAdaptTest.cs @@ -19,6 +19,8 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces { private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(3); + private static readonly ApproximateFloatComparer ApproximateComparer = new ApproximateFloatComparer(0.0001F); + [Theory] [InlineData(0, 0, 0, 0, 0, 0)] [InlineData(1, 1, 1, 1, 1, 1)] @@ -34,7 +36,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces Rgb output = converter.Adapt(input); // Assert - Assert.Equal(expectedOutput.WorkingSpace, output.WorkingSpace); + Assert.Equal(expectedOutput.WorkingSpace, output.WorkingSpace, ApproximateComparer); Assert.Equal(expectedOutput.R, output.R, FloatRoundingComparer); Assert.Equal(expectedOutput.G, output.G, FloatRoundingComparer); Assert.Equal(expectedOutput.B, output.B, FloatRoundingComparer); @@ -55,7 +57,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces Rgb output = converter.Adapt(input); // Assert - Assert.Equal(expectedOutput.WorkingSpace, output.WorkingSpace); + Assert.Equal(expectedOutput.WorkingSpace, output.WorkingSpace, ApproximateComparer); Assert.Equal(expectedOutput.R, output.R, FloatRoundingComparer); Assert.Equal(expectedOutput.G, output.G, FloatRoundingComparer); Assert.Equal(expectedOutput.B, output.B, FloatRoundingComparer); diff --git a/tests/ImageSharp.Tests/Colorspaces/RgbAndCieXyzConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/RgbAndCieXyzConversionTest.cs index ee71eefc1..48c91dd6d 100644 --- a/tests/ImageSharp.Tests/Colorspaces/RgbAndCieXyzConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/RgbAndCieXyzConversionTest.cs @@ -19,6 +19,8 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces { private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(6); + private static readonly ApproximateFloatComparer ApproximateComparer = new ApproximateFloatComparer(0.0001F); + /// /// Tests conversion from () /// to (default sRGB working space). @@ -40,7 +42,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces Rgb output = converter.ToRgb(input); // Assert - Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace); + Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace, ApproximateComparer); Assert.Equal(r, output.R, FloatRoundingComparer); Assert.Equal(g, output.G, FloatRoundingComparer); Assert.Equal(b, output.B, FloatRoundingComparer); @@ -68,7 +70,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces Rgb output = converter.ToRgb(input); // Assert - Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace); + Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace, ApproximateComparer); Assert.Equal(r, output.R, FloatRoundingComparer); Assert.Equal(g, output.G, FloatRoundingComparer); Assert.Equal(b, output.B, FloatRoundingComparer); diff --git a/tests/ImageSharp.Tests/Colorspaces/RgbAndCmykConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/RgbAndCmykConversionTest.cs index bc4cb1106..495ae2017 100644 --- a/tests/ImageSharp.Tests/Colorspaces/RgbAndCmykConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/RgbAndCmykConversionTest.cs @@ -22,6 +22,8 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + private static readonly ApproximateFloatComparer ApproximateComparer = new ApproximateFloatComparer(0.0001F); + /// /// Tests conversion from to . /// @@ -38,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces var output = Converter.ToRgb(input); // Assert - Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace); + Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace, ApproximateComparer); Assert.Equal(r, output.R, FloatRoundingComparer); Assert.Equal(g, output.G, FloatRoundingComparer); Assert.Equal(b, output.B, FloatRoundingComparer); diff --git a/tests/ImageSharp.Tests/Colorspaces/RgbAndHslConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/RgbAndHslConversionTest.cs index a7071e883..f658ddaae 100644 --- a/tests/ImageSharp.Tests/Colorspaces/RgbAndHslConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/RgbAndHslConversionTest.cs @@ -22,6 +22,8 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + private static readonly ApproximateFloatComparer ApproximateComparer = new ApproximateFloatComparer(0.0001F); + /// /// Tests conversion from to . /// @@ -41,7 +43,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces Rgb output = Converter.ToRgb(input); // Assert - Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace); + Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace, ApproximateComparer); Assert.Equal(r, output.R, FloatRoundingComparer); Assert.Equal(g, output.G, FloatRoundingComparer); Assert.Equal(b, output.B, FloatRoundingComparer); diff --git a/tests/ImageSharp.Tests/Colorspaces/RgbAndHsvConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/RgbAndHsvConversionTest.cs index 0dc58a0a3..63b3d9b74 100644 --- a/tests/ImageSharp.Tests/Colorspaces/RgbAndHsvConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/RgbAndHsvConversionTest.cs @@ -21,6 +21,8 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + private static readonly ApproximateFloatComparer ApproximateComparer = new ApproximateFloatComparer(0.0001F); + /// /// Tests conversion from to . /// @@ -40,7 +42,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces Rgb output = Converter.ToRgb(input); // Assert - Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace); + Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace, ApproximateComparer); Assert.Equal(r, output.R, FloatRoundingComparer); Assert.Equal(g, output.G, FloatRoundingComparer); Assert.Equal(b, output.B, FloatRoundingComparer); diff --git a/tests/ImageSharp.Tests/Colorspaces/RgbAndYCbCrConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/RgbAndYCbCrConversionTest.cs index 0eb1f620b..96c302e25 100644 --- a/tests/ImageSharp.Tests/Colorspaces/RgbAndYCbCrConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/RgbAndYCbCrConversionTest.cs @@ -20,6 +20,8 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + private static readonly ApproximateFloatComparer ApproximateComparer = new ApproximateFloatComparer(0.0001F); + /// /// Tests conversion from to . /// @@ -36,7 +38,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces Rgb output = Converter.ToRgb(input); // Assert - Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace); + Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace, ApproximateComparer); Assert.Equal(r, output.R, FloatRoundingComparer); Assert.Equal(g, output.G, FloatRoundingComparer); Assert.Equal(b, output.B, FloatRoundingComparer); diff --git a/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs b/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs index db6c1157c..79ebf4778 100644 --- a/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs @@ -1,16 +1,14 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using System.Numerics; -using SixLabors.ImageSharp; using SixLabors.ImageSharp.Drawing; using SixLabors.ImageSharp.Drawing.Pens; using SixLabors.ImageSharp.Drawing.Processors; -using SixLabors.ImageSharp.PixelFormats; using Moq; using Xunit; using SixLabors.ImageSharp.Drawing.Brushes; +using SixLabors.Primitives; namespace SixLabors.ImageSharp.Tests.Drawing { @@ -25,18 +23,18 @@ namespace SixLabors.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) { - SixLabors.Primitives.Rectangle bounds = new SixLabors.Primitives.Rectangle(0, 0, 1, 1); + var bounds = new SixLabors.Primitives.Rectangle(0, 0, 1, 1); - Mock> brush = new Mock>(); - Mock region = new Mock(); + var brush = new Mock>(); + var region = new Mock(); region.Setup(x => x.Bounds).Returns(bounds); - GraphicsOptions options = new GraphicsOptions(antialias) + var options = new GraphicsOptions(antialias) { AntialiasSubpixelDepth = 1 }; - FillRegionProcessor processor = new FillRegionProcessor(brush.Object, region.Object, options); - Image img = new Image(1, 1); + var processor = new FillRegionProcessor(brush.Object, region.Object, options); + var img = new Image(1, 1); processor.Apply(img, bounds); region.Verify(x => x.Scan(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(4)); @@ -45,31 +43,11 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void FillOffCanvas() { - - SixLabors.Primitives.Rectangle bounds = new SixLabors.Primitives.Rectangle(-100, -10, 10, 10); - - Mock> brush = new Mock>(); - Mock region = new Mock(); - region.Setup(x => x.Bounds).Returns(bounds); - - region.Setup(x => x.MaxIntersections).Returns(10); - region.Setup(x => x.Scan(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns>((y, span) => - { - if (y < 5) - { - span[0] = -10f; - span[1] = 100f; - return 2; - } - return 0; - }); - - GraphicsOptions options = new GraphicsOptions(true) - { - }; - FillRegionProcessor processor = new FillRegionProcessor(brush.Object, region.Object, options); - Image img = new Image(10, 10); + var bounds = new Rectangle(-100, -10, 10, 10); + var brush = new Mock>(); + var options = new GraphicsOptions(true); + var processor = new FillRegionProcessor(brush.Object, new MockRegion(), options); + var img = new Image(10, 10); processor.Apply(img, bounds); } @@ -85,5 +63,24 @@ namespace SixLabors.ImageSharp.Tests.Drawing })); } } + + // Mocking the region throws an error in netcore2.0 + private class MockRegion : Region + { + public override Rectangle Bounds => new Rectangle(-100, -10, 10, 10); + + public override int MaxIntersections => 10; + + public override int Scan(float y, float[] buffer, int offset) + { + if (y < 5) + { + buffer[0] = -10f; + buffer[1] = 100f; + return 2; + } + return 0; + } + } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index 7e0dc915c..f141905ef 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -39,10 +39,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [MemberData(nameof(CommonConversionData))] public void ConvertFromYCbCrBasic(int inputBufferLength, int resultBufferLength, int seed) { - ValidateConversion(new JpegColorConverter.FromYCbCrBasic(), 3, inputBufferLength, resultBufferLength, seed, ValidateYCbCr); + ValidateRgbToYCbCrConversion( + new JpegColorConverter.FromYCbCrBasic(), + 3, + inputBufferLength, + resultBufferLength, + seed); } - private static void ValidateYCbCr(JpegColorConverter.ComponentValues values, Span result, int i) + private static void ValidateYCbCr(JpegColorConverter.ComponentValues values, Vector4[] result, int i) { float y = values.Component0[i]; float cb = values.Component1[i]; @@ -63,20 +68,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(8, 3)] public void FromYCbCrSimd_ConvertCore(int size, int seed) { - ValidateConversion(JpegColorConverter.FromYCbCrSimd.ConvertCore, 3, size, size, seed, ValidateYCbCr); + JpegColorConverter.ComponentValues values = CreateRandomValues(3, size, seed); + Vector4[] result = new Vector4[size]; + + JpegColorConverter.FromYCbCrSimd.ConvertCore(values, result); + + for (int i = 0; i < size; i++) + { + ValidateYCbCr(values, result, i); + } } [Theory] [MemberData(nameof(CommonConversionData))] public void FromYCbCrSimd(int inputBufferLength, int resultBufferLength, int seed) { - ValidateConversion( + ValidateRgbToYCbCrConversion( new JpegColorConverter.FromYCbCrSimd(), 3, inputBufferLength, resultBufferLength, - seed, - ValidateYCbCr); + seed); } [Theory] @@ -91,13 +103,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg //JpegColorConverter.FromYCbCrSimdAvx2.LogPlz = s => this.Output.WriteLine(s); - ValidateConversion( + ValidateRgbToYCbCrConversion( new JpegColorConverter.FromYCbCrSimdAvx2(), 3, inputBufferLength, resultBufferLength, - seed, - ValidateYCbCr); + seed); } @@ -105,10 +116,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [MemberData(nameof(CommonConversionData))] public void ConvertFromYCbCr_WithDefaultConverter(int inputBufferLength, int resultBufferLength, int seed) { - ValidateConversion(JpegColorSpace.YCbCr, 3, inputBufferLength, resultBufferLength, seed, ValidateYCbCr); + ValidateConversion( + JpegColorSpace.YCbCr, + 3, + inputBufferLength, + resultBufferLength, + seed); } - // Becnhmark, for local execution only + // Benchmark, for local execution only //[Theory] //[InlineData(false)] //[InlineData(true)] @@ -120,11 +136,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg JpegColorConverter.ComponentValues values = CreateRandomValues(3, count, 1); Vector4[] result = new Vector4[count]; - JpegColorConverter converter = simd ? (JpegColorConverter)new JpegColorConverter.FromYCbCrSimd() : new JpegColorConverter.FromYCbCrBasic(); - + JpegColorConverter converter = simd ? (JpegColorConverter)new JpegColorConverter.FromYCbCrSimd() : new JpegColorConverter.FromYCbCrBasic(); + // Warm up: converter.ConvertToRGBA(values, result); - + using (new MeasureGuard(this.Output, $"{converter.GetType().Name} x {times}")) { for (int i = 0; i < times; i++) @@ -141,79 +157,79 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var v = new Vector4(0, 0, 0, 1F); var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); - ValidateConversion( - JpegColorSpace.Cmyk, - 4, - inputBufferLength, - resultBufferLength, - seed, - (values, result, i) => - { - float c = values.Component0[i]; - float m = values.Component1[i]; - float y = values.Component2[i]; - float k = values.Component3[i] / 255F; - - v.X = c * k; - v.Y = m * k; - v.Z = y * k; - v.W = 1F; - - v *= scale; - - Vector4 rgba = result[i]; - var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); - var expected = new Rgb(v.X, v.Y, v.Z); - - Assert.True(actual.AlmostEquals(expected, Precision)); - Assert.Equal(1, rgba.W); - }); + var converter = JpegColorConverter.GetConverter(JpegColorSpace.Cmyk); + JpegColorConverter.ComponentValues values = CreateRandomValues(4, inputBufferLength, seed); + Vector4[] result = new Vector4[resultBufferLength]; + + converter.ConvertToRGBA(values, result); + + for (int i = 0; i < resultBufferLength; i++) + { + float c = values.Component0[i]; + float m = values.Component1[i]; + float y = values.Component2[i]; + float k = values.Component3[i] / 255F; + + v.X = c * k; + v.Y = m * k; + v.Z = y * k; + v.W = 1F; + + v *= scale; + + Vector4 rgba = result[i]; + var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); + var expected = new Rgb(v.X, v.Y, v.Z); + + Assert.True(actual.AlmostEquals(expected, Precision)); + Assert.Equal(1, rgba.W); + } } [Theory] [MemberData(nameof(CommonConversionData))] public void ConvertFromGrayScale(int inputBufferLength, int resultBufferLength, int seed) { - ValidateConversion( - JpegColorSpace.GrayScale, - 1, - inputBufferLength, - resultBufferLength, - seed, - (values, result, i) => - { - float y = values.Component0[i]; - Vector4 rgba = result[i]; - var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); - var expected = new Rgb(y / 255F, y / 255F, y / 255F); - - Assert.True(actual.AlmostEquals(expected, Precision)); - Assert.Equal(1, rgba.W); - }); + var converter = JpegColorConverter.GetConverter(JpegColorSpace.GrayScale); + JpegColorConverter.ComponentValues values = CreateRandomValues(1, inputBufferLength, seed); + Vector4[] result = new Vector4[resultBufferLength]; + + converter.ConvertToRGBA(values, result); + + for (int i = 0; i < resultBufferLength; i++) + { + float y = values.Component0[i]; + Vector4 rgba = result[i]; + var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); + var expected = new Rgb(y / 255F, y / 255F, y / 255F); + + Assert.True(actual.AlmostEquals(expected, Precision)); + Assert.Equal(1, rgba.W); + } } [Theory] [MemberData(nameof(CommonConversionData))] public void ConvertFromRgb(int inputBufferLength, int resultBufferLength, int seed) { - ValidateConversion( - JpegColorSpace.RGB, - 3, - inputBufferLength, - resultBufferLength, - seed, - (values, result, i) => - { - float r = values.Component0[i]; - float g = values.Component1[i]; - float b = values.Component2[i]; - Vector4 rgba = result[i]; - var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); - var expected = new Rgb(r / 255F, g / 255F, b / 255F); - - Assert.True(actual.AlmostEquals(expected, Precision)); - Assert.Equal(1, rgba.W); - }); + var converter = JpegColorConverter.GetConverter(JpegColorSpace.RGB); + JpegColorConverter.ComponentValues values = CreateRandomValues(3, inputBufferLength, seed); + Vector4[] result = new Vector4[resultBufferLength]; + + converter.ConvertToRGBA(values, result); + + for (int i = 0; i < resultBufferLength; i++) + { + float r = values.Component0[i]; + float g = values.Component1[i]; + float b = values.Component2[i]; + Vector4 rgba = result[i]; + var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); + var expected = new Rgb(r / 255F, g / 255F, b / 255F); + + Assert.True(actual.AlmostEquals(expected, Precision)); + Assert.Equal(1, rgba.W); + } } [Theory] @@ -223,35 +239,35 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var v = new Vector4(0, 0, 0, 1F); var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); - ValidateConversion( - JpegColorSpace.Ycck, - 4, - inputBufferLength, - resultBufferLength, - seed, - (values, result, i) => - { - float y = values.Component0[i]; - float cb = values.Component1[i] - 128F; - float cr = values.Component2[i] - 128F; - float k = values.Component3[i] / 255F; - - v.X = (255F - (float)Math.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k; - v.Y = (255F - (float)Math.Round( - y - (0.344136F * cb) - (0.714136F * cr), - MidpointRounding.AwayFromZero)) * k; - v.Z = (255F - (float)Math.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k; - v.W = 1F; - - v *= scale; - - Vector4 rgba = result[i]; - var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); - var expected = new Rgb(v.X, v.Y, v.Z); - - Assert.True(actual.AlmostEquals(expected, Precision)); - Assert.Equal(1, rgba.W); - }); + var converter = JpegColorConverter.GetConverter(JpegColorSpace.Ycck); + JpegColorConverter.ComponentValues values = CreateRandomValues(4, inputBufferLength, seed); + Vector4[] result = new Vector4[resultBufferLength]; + + converter.ConvertToRGBA(values, result); + + for (int i = 0; i < resultBufferLength; i++) + { + float y = values.Component0[i]; + float cb = values.Component1[i] - 128F; + float cr = values.Component2[i] - 128F; + float k = values.Component3[i] / 255F; + + v.X = (255F - (float)Math.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k; + v.Y = (255F - (float)Math.Round( + y - (0.344136F * cb) - (0.714136F * cr), + MidpointRounding.AwayFromZero)) * k; + v.Z = (255F - (float)Math.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k; + v.W = 1F; + + v *= scale; + + Vector4 rgba = result[i]; + var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); + var expected = new Rgb(v.X, v.Y, v.Z); + + Assert.True(actual.AlmostEquals(expected, Precision)); + Assert.Equal(1, rgba.W); + } } private static JpegColorConverter.ComponentValues CreateRandomValues( @@ -269,7 +285,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg for (int j = 0; j < inputBufferLength; j++) { - values[j] = (float)rnd.NextDouble() * (maxVal-minVal)+minVal; + values[j] = (float)rnd.NextDouble() * (maxVal - minVal) + minVal; } // no need to dispose when buffer is not array owner @@ -283,51 +299,31 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg int componentCount, int inputBufferLength, int resultBufferLength, - int seed, - Action, int> validatePixelValue) + int seed) { - ValidateConversion( + ValidateRgbToYCbCrConversion( JpegColorConverter.GetConverter(colorSpace), componentCount, inputBufferLength, resultBufferLength, - seed, - validatePixelValue); + seed); } - private static void ValidateConversion( + private static void ValidateRgbToYCbCrConversion( JpegColorConverter converter, int componentCount, int inputBufferLength, int resultBufferLength, - int seed, - Action, int> validatePixelValue) - { - ValidateConversion( - converter.ConvertToRGBA, - componentCount, - inputBufferLength, - resultBufferLength, - seed, - validatePixelValue); - } - - private static void ValidateConversion( - Action> doConvert, - int componentCount, - int inputBufferLength, - int resultBufferLength, - int seed, - Action, int> validatePixelValue) + int seed) { JpegColorConverter.ComponentValues values = CreateRandomValues(componentCount, inputBufferLength, seed); Vector4[] result = new Vector4[resultBufferLength]; - doConvert(values, result); + converter.ConvertToRGBA(values, result); for (int i = 0; i < resultBufferLength; i++) { - validatePixelValue(values, result, i); + ValidateYCbCr(values, result, i); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs index ae7a9c046..5a4db87b9 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils this.ComponentCount = components.Length; this.Components = components; } - + public static SpectralData LoadFromImageSharpDecoder(PdfJsJpegDecoderCore decoder) { PdfJsFrameComponent[] srcComponents = decoder.Frame.Components; @@ -137,12 +137,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils public static bool operator ==(SpectralData left, SpectralData right) { - return Object.Equals(left, right); + if (ReferenceEquals(left, right)) + { + return true; + } + + return left.Equals(right); } public static bool operator !=(SpectralData left, SpectralData right) { - return !Object.Equals(left, right); + return !(left == right); } } } diff --git a/tests/ImageSharp.Tests/Image/ImageDiscoverMimeType.cs b/tests/ImageSharp.Tests/Image/ImageDiscoverMimeType.cs index aefa32f46..f19fa1990 100644 --- a/tests/ImageSharp.Tests/Image/ImageDiscoverMimeType.cs +++ b/tests/ImageSharp.Tests/Image/ImageDiscoverMimeType.cs @@ -5,7 +5,6 @@ using System; using System.IO; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.PixelFormats; using Moq; using Xunit; @@ -18,10 +17,10 @@ namespace SixLabors.ImageSharp.Tests { private readonly Mock fileSystem; private readonly string FilePath; - private readonly Mock localMimeTypeDetector; + private readonly IImageFormatDetector localMimeTypeDetector; private readonly Mock localImageFormatMock; - public IImageFormat localImageFormat => localImageFormatMock.Object; + public IImageFormat localImageFormat => this.localImageFormatMock.Object; public Configuration LocalConfiguration { get; private set; } public byte[] Marker { get; private set; } public MemoryStream DataStream { get; private set; } @@ -32,9 +31,7 @@ namespace SixLabors.ImageSharp.Tests { this.localImageFormatMock = new Mock(); - this.localMimeTypeDetector = new Mock(); - this.localMimeTypeDetector.Setup(x => x.HeaderSize).Returns(1); - this.localMimeTypeDetector.Setup(x => x.DetectFormat(It.IsAny>())).Returns(localImageFormatMock.Object); + this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormatMock.Object); this.fileSystem = new Mock(); @@ -42,7 +39,8 @@ namespace SixLabors.ImageSharp.Tests { FileSystem = this.fileSystem.Object }; - this.LocalConfiguration.AddImageFormatDetector(this.localMimeTypeDetector.Object); + + this.LocalConfiguration.AddImageFormatDetector(this.localMimeTypeDetector); TestFormat.RegisterGlobalTestFormat(); this.Marker = Guid.NewGuid().ToByteArray(); @@ -58,49 +56,49 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void DiscoverImageFormatByteArray() { - var type = Image.DetectFormat(DataStream.ToArray()); + IImageFormat type = Image.DetectFormat(this.DataStream.ToArray()); Assert.Equal(TestFormat.GlobalTestFormat, type); } [Fact] public void DiscoverImageFormatByteArray_WithConfig() { - var type = Image.DetectFormat(this.LocalConfiguration, DataStream.ToArray()); - Assert.Equal(localImageFormat, type); + IImageFormat type = Image.DetectFormat(this.LocalConfiguration, this.DataStream.ToArray()); + Assert.Equal(this.localImageFormat, type); } [Fact] public void DiscoverImageFormatFile() { - var type = Image.DetectFormat(this.FilePath); + IImageFormat type = Image.DetectFormat(this.FilePath); Assert.Equal(TestFormat.GlobalTestFormat, type); } [Fact] public void DiscoverImageFormatFilePath_WithConfig() { - var type = Image.DetectFormat(this.LocalConfiguration, FilePath); - Assert.Equal(localImageFormat, type); + IImageFormat type = Image.DetectFormat(this.LocalConfiguration, this.FilePath); + Assert.Equal(this.localImageFormat, type); } [Fact] public void DiscoverImageFormatStream() { - var type = Image.DetectFormat(this.DataStream); + IImageFormat type = Image.DetectFormat(this.DataStream); Assert.Equal(TestFormat.GlobalTestFormat, type); } [Fact] public void DiscoverImageFormatFileStream_WithConfig() { - var type = Image.DetectFormat(this.LocalConfiguration, DataStream); - Assert.Equal(localImageFormat, type); + IImageFormat type = Image.DetectFormat(this.LocalConfiguration, this.DataStream); + Assert.Equal(this.localImageFormat, type); } [Fact] public void DiscoverImageFormatNoDetectorsRegisterdShouldReturnNull() { - var type = Image.DetectFormat(new Configuration(), DataStream); + IImageFormat type = Image.DetectFormat(new Configuration(), this.DataStream); Assert.Null(type); } } diff --git a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs index 2c0a30b15..de18714e2 100644 --- a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs @@ -14,13 +14,13 @@ namespace SixLabors.ImageSharp.Tests /// /// Tests the class. /// - public class ImageLoadTests : IDisposable + public partial class ImageLoadTests : IDisposable { private readonly Mock fileSystem; private Image returnImage; private Mock localDecoder; private readonly string FilePath; - private readonly Mock localMimeTypeDetector; + private readonly IImageFormatDetector localMimeTypeDetector; private readonly Mock localImageFormatMock; public Configuration LocalConfiguration { get; private set; } @@ -35,10 +35,7 @@ namespace SixLabors.ImageSharp.Tests this.localImageFormatMock = new Mock(); this.localDecoder = new Mock(); - this.localMimeTypeDetector = new Mock(); - this.localMimeTypeDetector.Setup(x => x.HeaderSize).Returns(1); - this.localMimeTypeDetector.Setup(x => x.DetectFormat(It.IsAny>())).Returns(localImageFormatMock.Object); - + this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormatMock.Object); this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny())) .Callback((c, s) => @@ -57,8 +54,8 @@ namespace SixLabors.ImageSharp.Tests { FileSystem = this.fileSystem.Object }; - this.LocalConfiguration.AddImageFormatDetector(this.localMimeTypeDetector.Object); - this.LocalConfiguration.SetDecoder(localImageFormatMock.Object, this.localDecoder.Object); + this.LocalConfiguration.AddImageFormatDetector(this.localMimeTypeDetector); + this.LocalConfiguration.SetDecoder(this.localImageFormatMock.Object, this.localDecoder.Object); TestFormat.RegisterGlobalTestFormat(); this.Marker = Guid.NewGuid().ToByteArray(); diff --git a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs index 5b672059c..7f6e3b7da 100644 --- a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs @@ -24,17 +24,14 @@ namespace SixLabors.ImageSharp.Tests private readonly Mock fileSystem; private readonly Mock encoder; private readonly Mock encoderNotInFormat; - private Mock localMimeTypeDetector; + private IImageFormatDetector localMimeTypeDetector; private Mock localImageFormat; public ImageSaveTests() { this.localImageFormat = new Mock(); this.localImageFormat.Setup(x => x.FileExtensions).Returns(new[] { "png" }); - - this.localMimeTypeDetector = new Mock(); - this.localMimeTypeDetector.Setup(x => x.HeaderSize).Returns(1); - this.localMimeTypeDetector.Setup(x => x.DetectFormat(It.IsAny>())).Returns(localImageFormat.Object); + this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormat.Object); this.encoder = new Mock(); @@ -45,8 +42,8 @@ namespace SixLabors.ImageSharp.Tests { FileSystem = this.fileSystem.Object }; - config.AddImageFormatDetector(this.localMimeTypeDetector.Object); - config.SetEncoder(localImageFormat.Object, this.encoder.Object); + config.AddImageFormatDetector(this.localMimeTypeDetector); + config.SetEncoder(this.localImageFormat.Object, this.encoder.Object); this.Image = new Image(config, 1, 1); } @@ -57,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests { using (Image image = provider.GetImage()) { - TPixel[] buffer = new TPixel[image.Width*image.Height]; + TPixel[] buffer = new TPixel[image.Width * image.Height]; image.SavePixelData(buffer); image.ComparePixelBufferTo(buffer); @@ -73,14 +70,14 @@ namespace SixLabors.ImageSharp.Tests { using (Image image = provider.GetImage()) { - byte[] buffer = new byte[image.Width*image.Height*Unsafe.SizeOf()]; + byte[] buffer = new byte[image.Width * image.Height * Unsafe.SizeOf()]; image.SavePixelData(buffer); image.ComparePixelBufferTo(buffer.AsSpan().NonPortableCast()); } } - + [Fact] public void SavePixelData_Rgba32_WhenBufferIsTooSmall_Throws() { @@ -91,7 +88,7 @@ namespace SixLabors.ImageSharp.Tests img[0, 1] = Rgba32.Red; img[1, 1] = Rgba32.Blue; - var buffer = new byte[2 * 2]; // width * height * bytes per pixel + byte[] buffer = new byte[2 * 2]; // width * height * bytes per pixel Assert.Throws(() => { @@ -125,7 +122,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void ToBase64String() { - var str = this.Image.ToBase64String(localImageFormat.Object); + string str = this.Image.ToBase64String(this.localImageFormat.Object); this.encoder.Verify(x => x.Encode(this.Image, It.IsAny())); } @@ -134,7 +131,7 @@ namespace SixLabors.ImageSharp.Tests public void SaveStreamWithMime() { Stream stream = new MemoryStream(); - this.Image.Save(stream, localImageFormat.Object); + this.Image.Save(stream, this.localImageFormat.Object); this.encoder.Verify(x => x.Encode(this.Image, stream)); } diff --git a/tests/ImageSharp.Tests/Image/MockImageFormatDetector.cs b/tests/ImageSharp.Tests/Image/MockImageFormatDetector.cs new file mode 100644 index 000000000..cb09fa010 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/MockImageFormatDetector.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Formats; + +namespace SixLabors.ImageSharp.Tests +{ + /// + /// You can't mock the "DetectFormat" method due to the ReadOnlySpan{byte} parameter. + /// + public class MockImageFormatDetector : IImageFormatDetector + { + private IImageFormat localImageFormatMock; + + public MockImageFormatDetector(IImageFormat imageFormat) + { + this.localImageFormatMock = imageFormat; + } + + public int HeaderSize => 1; + + public IImageFormat DetectFormat(ReadOnlySpan header) + { + return this.localImageFormatMock; + } + } +} diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index 4f214fd85..16f062c6e 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -1,6 +1,6 @@  - netcoreapp1.1 + netcoreapp2.0 True full portable @@ -16,14 +16,14 @@ - + - - - + + + diff --git a/tests/ImageSharp.Tests/Memory/Fast2DArrayTests.cs b/tests/ImageSharp.Tests/Memory/Fast2DArrayTests.cs index 5cdbe638a..a5364db72 100644 --- a/tests/ImageSharp.Tests/Memory/Fast2DArrayTests.cs +++ b/tests/ImageSharp.Tests/Memory/Fast2DArrayTests.cs @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Memory { Assert.Throws(() => { - Fast2DArray fast = new Fast2DArray(null); + var fast = new Fast2DArray(null); }); } @@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Tests.Memory { Assert.Throws(() => { - Fast2DArray fast = new Fast2DArray(0, 10); + var fast = new Fast2DArray(0, 10); }); } @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Memory { Assert.Throws(() => { - Fast2DArray fast = new Fast2DArray(10, 0); + var fast = new Fast2DArray(10, 0); }); } @@ -49,14 +49,14 @@ namespace SixLabors.ImageSharp.Tests.Memory { Assert.Throws(() => { - Fast2DArray fast = new Fast2DArray(new float[0, 0]); + var fast = new Fast2DArray(new float[0, 0]); }); } [Fact] public void Fast2DArrayReturnsCorrectDimensions() { - Fast2DArray fast = new Fast2DArray(FloydSteinbergMatrix); + var fast = new Fast2DArray(FloydSteinbergMatrix); Assert.True(fast.Width == FloydSteinbergMatrix.GetLength(1)); Assert.True(fast.Height == FloydSteinbergMatrix.GetLength(0)); } @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Tests.Memory [Fact] public void Fast2DArrayGetSetReturnsCorrectResults() { - Fast2DArray fast = new Fast2DArray(4, 4); + var fast = new Fast2DArray(4, 4); const float Val = 5F; fast[3, 3] = Val; diff --git a/tests/ImageSharp.Tests/PixelFormats/ColorDefinitionTests.cs b/tests/ImageSharp.Tests/PixelFormats/ColorDefinitionTests.cs index af4181cde..302e56ec7 100644 --- a/tests/ImageSharp.Tests/PixelFormats/ColorDefinitionTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/ColorDefinitionTests.cs @@ -16,7 +16,8 @@ namespace SixLabors.ImageSharp.Tests get { var result = new TheoryData(); - foreach (string name in typeof(NamedColors).GetTypeInfo().GetFields().Select(x => x.Name )) + foreach (string name in typeof(NamedColors).GetTypeInfo() + .GetFields().Where(x => x.Name != nameof(NamedColors.WebSafePalette)).Select(x => x.Name)) { result.Add(name); } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.cs index 0fde67d28..945a4f502 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.cs @@ -387,13 +387,13 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats internal static void TestOperation( TSource[] source, TDest[] expected, - Action, Buffer> action) + Action, Buffer> action) where TSource : struct where TDest : struct { - using (TestBuffers buffers = new TestBuffers(source, expected)) + using (var buffers = new TestBuffers(source, expected)) { - action(buffers.Source, buffers.ActualDestBuffer); + action(buffers.SourceBuffer, buffers.ActualDestBuffer); buffers.Verify(); } } diff --git a/tests/ImageSharp.Tests/Processing/Binarization/BinaryDitherTest.cs b/tests/ImageSharp.Tests/Processing/Binarization/BinaryDitherTest.cs new file mode 100644 index 000000000..003f998d8 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Binarization/BinaryDitherTest.cs @@ -0,0 +1,105 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Dithering; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Binarization +{ + public class BinaryDitherTest : BaseImageOperationsExtensionTest + { + private readonly IOrderedDither orderedDither; + private readonly IErrorDiffuser errorDiffuser; + + public BinaryDitherTest() + { + this.orderedDither = KnownDitherers.BayerDither4x4; + this.errorDiffuser = KnownDiffusers.FloydSteinberg; + } + + [Fact] + public void BinaryDither_CorrectProcessor() + { + this.operations.BinaryDither(this.orderedDither); + BinaryOrderedDitherProcessor p = this.Verify>(); + Assert.Equal(this.orderedDither, p.Dither); + Assert.Equal(NamedColors.White, p.UpperColor); + Assert.Equal(NamedColors.Black, p.LowerColor); + } + + [Fact] + public void BinaryDither_rect_CorrectProcessor() + { + this.operations.BinaryDither(this.orderedDither, this.rect); + BinaryOrderedDitherProcessor p = this.Verify>(this.rect); + Assert.Equal(this.orderedDither, p.Dither); + Assert.Equal(NamedColors.White, p.UpperColor); + Assert.Equal(NamedColors.Black, p.LowerColor); + } + [Fact] + public void BinaryDither_index_CorrectProcessor() + { + this.operations.BinaryDither(this.orderedDither, NamedColors.Yellow, NamedColors.HotPink); + BinaryOrderedDitherProcessor p = this.Verify>(); + Assert.Equal(this.orderedDither, p.Dither); + Assert.Equal(NamedColors.Yellow, p.UpperColor); + Assert.Equal(NamedColors.HotPink, p.LowerColor); + } + + [Fact] + public void BinaryDither_index_rect_CorrectProcessor() + { + this.operations.BinaryDither(this.orderedDither, NamedColors.Yellow, NamedColors.HotPink, this.rect); + BinaryOrderedDitherProcessor p = this.Verify>(this.rect); + Assert.Equal(this.orderedDither, p.Dither); + Assert.Equal(NamedColors.HotPink, p.LowerColor); + } + + + [Fact] + public void BinaryDither_ErrorDiffuser_CorrectProcessor() + { + this.operations.BinaryDiffuse(this.errorDiffuser, .4F); + BinaryErrorDiffusionProcessor p = this.Verify>(); + Assert.Equal(this.errorDiffuser, p.Diffuser); + Assert.Equal(.4F, p.Threshold); + Assert.Equal(NamedColors.White, p.UpperColor); + Assert.Equal(NamedColors.Black, p.LowerColor); + } + + [Fact] + public void BinaryDither_ErrorDiffuser_rect_CorrectProcessor() + { + this.operations.BinaryDiffuse(this.errorDiffuser, .3F, this.rect); + BinaryErrorDiffusionProcessor p = this.Verify>(this.rect); + Assert.Equal(this.errorDiffuser, p.Diffuser); + Assert.Equal(.3F, p.Threshold); + Assert.Equal(NamedColors.White, p.UpperColor); + Assert.Equal(NamedColors.Black, p.LowerColor); + } + + [Fact] + public void BinaryDither_ErrorDiffuser_CorrectProcessorWithColors() + { + this.operations.BinaryDiffuse(this.errorDiffuser, .5F, NamedColors.HotPink, NamedColors.Yellow); + BinaryErrorDiffusionProcessor p = this.Verify>(); + Assert.Equal(this.errorDiffuser, p.Diffuser); + Assert.Equal(.5F, p.Threshold); + Assert.Equal(NamedColors.HotPink, p.UpperColor); + Assert.Equal(NamedColors.Yellow, p.LowerColor); + } + + [Fact] + public void BinaryDither_ErrorDiffuser_rect_CorrectProcessorWithColors() + { + this.operations.BinaryDiffuse(this.errorDiffuser, .5F, NamedColors.HotPink, NamedColors.Yellow, this.rect); + BinaryErrorDiffusionProcessor p = this.Verify>(this.rect); + Assert.Equal(this.errorDiffuser, p.Diffuser); + Assert.Equal(.5F, p.Threshold); + Assert.Equal(NamedColors.HotPink, p.UpperColor); + Assert.Equal(NamedColors.Yellow, p.LowerColor); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs b/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs index 221b4a9bf..488b3d18b 100644 --- a/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs +++ b/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs @@ -15,16 +15,40 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization public void BinaryThreshold_CorrectProcessor() { this.operations.BinaryThreshold(.23f); - var p = this.Verify>(); + BinaryThresholdProcessor p = this.Verify>(); Assert.Equal(.23f, p.Threshold); + Assert.Equal(NamedColors.White, p.UpperColor); + Assert.Equal(NamedColors.Black, p.LowerColor); } [Fact] public void BinaryThreshold_rect_CorrectProcessor() { this.operations.BinaryThreshold(.93f, this.rect); - var p = this.Verify>(this.rect); + BinaryThresholdProcessor p = this.Verify>(this.rect); Assert.Equal(.93f, p.Threshold); + Assert.Equal(NamedColors.White, p.UpperColor); + Assert.Equal(NamedColors.Black, p.LowerColor); + } + + [Fact] + public void BinaryThreshold_CorrectProcessorWithUpperLower() + { + this.operations.BinaryThreshold(.23f, NamedColors.HotPink, NamedColors.Yellow); + BinaryThresholdProcessor p = this.Verify>(); + Assert.Equal(.23f, p.Threshold); + Assert.Equal(NamedColors.HotPink, p.UpperColor); + Assert.Equal(NamedColors.Yellow, p.LowerColor); + } + + [Fact] + public void BinaryThreshold_rect_CorrectProcessorWithUpperLower() + { + this.operations.BinaryThreshold(.93f, NamedColors.HotPink, NamedColors.Yellow, this.rect); + BinaryThresholdProcessor p = this.Verify>(this.rect); + Assert.Equal(.93f, p.Threshold); + Assert.Equal(NamedColors.HotPink, p.UpperColor); + Assert.Equal(NamedColors.Yellow, p.LowerColor); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Binarization/DitherTests.cs b/tests/ImageSharp.Tests/Processing/Binarization/DitherTests.cs deleted file mode 100644 index 94241d007..000000000 --- a/tests/ImageSharp.Tests/Processing/Binarization/DitherTests.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Dithering; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors; -using Moq; -using SixLabors.Primitives; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Processing.Binarization -{ - public class DitherTest : BaseImageOperationsExtensionTest - { - private readonly IOrderedDither orderedDither; - private readonly IErrorDiffuser errorDiffuser; - - public DitherTest() - { - this.orderedDither = new Mock().Object; - this.errorDiffuser = new Mock().Object; - } - [Fact] - public void Dither_CorrectProcessor() - { - this.operations.Dither(orderedDither); - var p = this.Verify>(); - Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(0, p.Index); - } - - [Fact] - public void Dither_rect_CorrectProcessor() - { - this.operations.Dither(orderedDither, this.rect); - var p = this.Verify>(this.rect); - Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(0, p.Index); - } - [Fact] - public void Dither_index_CorrectProcessor() - { - this.operations.Dither(orderedDither, 2); - var p = this.Verify>(); - Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(2, p.Index); - } - - [Fact] - public void Dither_index_rect_CorrectProcessor() - { - this.operations.Dither(orderedDither, this.rect, 2); - var p = this.Verify>(this.rect); - Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(2, p.Index); - } - - - [Fact] - public void Dither_ErrorDifuser_CorrectProcessor() - { - this.operations.Dither(errorDiffuser, 4); - var p = this.Verify>(); - Assert.Equal(this.errorDiffuser, p.Diffuser); - Assert.Equal(4, p.Threshold); - } - - [Fact] - public void Dither_ErrorDifuser_rect_CorrectProcessor() - { - this.operations.Dither(this.errorDiffuser, 3, this.rect); - var p = this.Verify>(this.rect); - Assert.Equal(this.errorDiffuser, p.Diffuser); - Assert.Equal(3, p.Threshold); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs b/tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs new file mode 100644 index 000000000..a0ddc2c7c --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs @@ -0,0 +1,102 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Dithering; +using SixLabors.ImageSharp.Memory; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Binarization +{ + public class OrderedDitherFactoryTests + { + private static readonly Fast2DArray Expected2x2Matrix = new Fast2DArray( + new uint[2, 2] + { + { 0, 2 }, + { 3, 1 } + }); + + private static readonly Fast2DArray Expected3x3Matrix = new Fast2DArray( + new uint[3, 3] + { + { 0, 5, 2 }, + { 7, 4, 8 }, + { 3, 6, 1 } + }); + + private static readonly Fast2DArray Expected4x4Matrix = new Fast2DArray( + new uint[4, 4] + { + { 0, 8, 2, 10 }, + { 12, 4, 14, 6 }, + { 3, 11, 1, 9 }, + { 15, 7, 13, 5 } + }); + + private static readonly Fast2DArray Expected8x8Matrix = new Fast2DArray( + new uint[8, 8] + { + { 0, 32, 8, 40, 2, 34, 10, 42 }, + { 48, 16, 56, 24, 50, 18, 58, 26 }, + { 12, 44, 4, 36, 14, 46, 6, 38 }, + { 60, 28, 52, 20, 62, 30, 54, 22 }, + { 3, 35, 11, 43, 1, 33, 9, 41 }, + { 51, 19, 59, 27, 49, 17, 57, 25 }, + { 15, 47, 7, 39, 13, 45, 5, 37 }, + { 63, 31, 55, 23, 61, 29, 53, 21 } + }); + + + [Fact] + public void OrderedDitherFactoryCreatesCorrect2x2Matrix() + { + Fast2DArray actual = OrderedDitherFactory.CreateDitherMatrix(2); + for (int y = 0; y < actual.Height; y++) + { + for (int x = 0; x < actual.Width; x++) + { + Assert.Equal(Expected2x2Matrix[y, x], actual[y, x]); + } + } + } + + [Fact] + public void OrderedDitherFactoryCreatesCorrect3x3Matrix() + { + Fast2DArray actual = OrderedDitherFactory.CreateDitherMatrix(3); + for (int y = 0; y < actual.Height; y++) + { + for (int x = 0; x < actual.Width; x++) + { + Assert.Equal(Expected3x3Matrix[y, x], actual[y, x]); + } + } + } + + [Fact] + public void OrderedDitherFactoryCreatesCorrect4x4Matrix() + { + Fast2DArray actual = OrderedDitherFactory.CreateDitherMatrix(4); + for (int y = 0; y < actual.Height; y++) + { + for (int x = 0; x < actual.Width; x++) + { + Assert.Equal(Expected4x4Matrix[y, x], actual[y, x]); + } + } + } + + [Fact] + public void OrderedDitherFactoryCreatesCorrect8x8Matrix() + { + Fast2DArray actual = OrderedDitherFactory.CreateDitherMatrix(8); + for (int y = 0; y < actual.Height; y++) + { + for (int x = 0; x < actual.Width; x++) + { + Assert.Equal(Expected8x8Matrix[y, x], actual[y, x]); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs b/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs new file mode 100644 index 000000000..03ae17848 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs @@ -0,0 +1,104 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Dithering; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Binarization +{ + public class DitherTest : BaseImageOperationsExtensionTest + { + private readonly IOrderedDither orderedDither; + private readonly IErrorDiffuser errorDiffuser; + private readonly Rgba32[] TestPalette = + { + Rgba32.Red, + Rgba32.Green, + Rgba32.Blue + }; + + public DitherTest() + { + this.orderedDither = KnownDitherers.BayerDither4x4; + this.errorDiffuser = KnownDiffusers.FloydSteinberg; + } + + [Fact] + public void Dither_CorrectProcessor() + { + this.operations.Dither(this.orderedDither); + OrderedDitherPaletteProcessor p = this.Verify>(); + Assert.Equal(this.orderedDither, p.Dither); + Assert.Equal(NamedColors.WebSafePalette, p.Palette); + } + + [Fact] + public void Dither_rect_CorrectProcessor() + { + this.operations.Dither(this.orderedDither, this.rect); + OrderedDitherPaletteProcessor p = this.Verify>(this.rect); + Assert.Equal(this.orderedDither, p.Dither); + Assert.Equal(NamedColors.WebSafePalette, p.Palette); + } + [Fact] + public void Dither_index_CorrectProcessor() + { + this.operations.Dither(this.orderedDither, this.TestPalette); + OrderedDitherPaletteProcessor p = this.Verify>(); + Assert.Equal(this.orderedDither, p.Dither); + Assert.Equal(this.TestPalette, p.Palette); + } + + [Fact] + public void Dither_index_rect_CorrectProcessor() + { + this.operations.Dither(this.orderedDither, this.TestPalette, this.rect); + OrderedDitherPaletteProcessor p = this.Verify>(this.rect); + Assert.Equal(this.orderedDither, p.Dither); + Assert.Equal(this.TestPalette, p.Palette); + } + + + [Fact] + public void Dither_ErrorDiffuser_CorrectProcessor() + { + this.operations.Diffuse(this.errorDiffuser, .4F); + ErrorDiffusionPaletteProcessor p = this.Verify>(); + Assert.Equal(this.errorDiffuser, p.Diffuser); + Assert.Equal(.4F, p.Threshold); + Assert.Equal(NamedColors.WebSafePalette, p.Palette); + } + + [Fact] + public void Dither_ErrorDiffuser_rect_CorrectProcessor() + { + this.operations.Diffuse(this.errorDiffuser, .3F, this.rect); + ErrorDiffusionPaletteProcessor p = this.Verify>(this.rect); + Assert.Equal(this.errorDiffuser, p.Diffuser); + Assert.Equal(.3F, p.Threshold); + Assert.Equal(NamedColors.WebSafePalette, p.Palette); + } + + [Fact] + public void Dither_ErrorDiffuser_CorrectProcessorWithColors() + { + this.operations.Diffuse(this.errorDiffuser, .5F, this.TestPalette); + ErrorDiffusionPaletteProcessor p = this.Verify>(); + Assert.Equal(this.errorDiffuser, p.Diffuser); + Assert.Equal(.5F, p.Threshold); + Assert.Equal(this.TestPalette, p.Palette); + } + + [Fact] + public void Dither_ErrorDiffuser_rect_CorrectProcessorWithColors() + { + this.operations.Diffuse(this.errorDiffuser, .5F, this.TestPalette, this.rect); + ErrorDiffusionPaletteProcessor p = this.Verify>(this.rect); + Assert.Equal(this.errorDiffuser, p.Diffuser); + Assert.Equal(.5F, p.Threshold); + Assert.Equal(this.TestPalette, p.Palette); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs new file mode 100644 index 000000000..aec201239 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs @@ -0,0 +1,131 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Dithering; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + +using SixLabors.Primitives; +using Xunit; +// ReSharper disable InconsistentNaming + +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization +{ + public class BinaryDitherTests : FileTestBase + { + public static readonly string[] CommonTestImages = + { + TestImages.Png.CalliphoraPartial, TestImages.Png.Bike + }; + + public static readonly TheoryData Ditherers = new TheoryData + { + { "Bayer8x8", KnownDitherers.BayerDither8x8 }, + { "Bayer4x4", KnownDitherers.BayerDither4x4 }, + { "Ordered3x3", KnownDitherers.OrderedDither3x3 }, + { "Bayer2x2", KnownDitherers.BayerDither2x2 } + }; + + public static readonly TheoryData ErrorDiffusers = new TheoryData + { + { "Atkinson", KnownDiffusers.Atkinson }, + { "Burks", KnownDiffusers.Burks }, + { "FloydSteinberg", KnownDiffusers.FloydSteinberg }, + { "JarvisJudiceNinke", KnownDiffusers.JarvisJudiceNinke }, + { "Sierra2", KnownDiffusers.Sierra2 }, + { "Sierra3", KnownDiffusers.Sierra3 }, + { "SierraLite", KnownDiffusers.SierraLite }, + { "StevensonArce", KnownDiffusers.StevensonArce }, + { "Stucki", KnownDiffusers.Stucki }, + }; + + + private static IOrderedDither DefaultDitherer => KnownDitherers.BayerDither4x4; + + private static IErrorDiffuser DefaultErrorDiffuser => KnownDiffusers.Atkinson; + + [Theory] + [WithFileCollection(nameof(CommonTestImages), nameof(Ditherers), DefaultPixelType)] + [WithTestPatternImages(nameof(Ditherers), 100, 100, DefaultPixelType)] + public void BinaryDitherFilter_WorksWithAllDitherers(TestImageProvider provider, string name, IOrderedDither ditherer) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.BinaryDither(ditherer)); + image.DebugSave(provider, name); + } + } + + [Theory] + [WithFileCollection(nameof(CommonTestImages), nameof(ErrorDiffusers), DefaultPixelType)] + [WithTestPatternImages(nameof(ErrorDiffusers), 100, 100, DefaultPixelType)] + public void DiffusionFilter_WorksWithAllErrorDiffusers(TestImageProvider provider, string name, IErrorDiffuser diffuser) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.BinaryDiffuse(diffuser, .5F)); + image.DebugSave(provider, name); + } + } + + [Theory] + [WithFile(TestImages.Png.Bike, CommonNonDefaultPixelTypes)] + public void BinaryDitherFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.BinaryDither(DefaultDitherer)); + image.DebugSave(provider); + } + } + + [Theory] + [WithFile(TestImages.Png.Bike, CommonNonDefaultPixelTypes)] + public void DiffusionFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.BinaryDiffuse(DefaultErrorDiffuser, 0.5f)); + image.DebugSave(provider); + } + } + + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, DefaultPixelType)] + public void ApplyDitherFilterInBox(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image source = provider.GetImage()) + using (Image image = source.Clone()) + { + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + + image.Mutate(x => x.BinaryDither(DefaultDitherer, bounds)); + image.DebugSave(provider); + + ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); + } + } + + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, DefaultPixelType)] + public void ApplyDiffusionFilterInBox(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image source = provider.GetImage()) + using (Image image = source.Clone()) + { + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + + image.Mutate(x => x.BinaryDiffuse(DefaultErrorDiffuser, .5F, bounds)); + image.DebugSave(provider); + + ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Binarization/DitherTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs similarity index 78% rename from tests/ImageSharp.Tests/Processing/Processors/Binarization/DitherTests.cs rename to tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs index 9a6d24226..58c63d48c 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Binarization/DitherTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Dithering; +using SixLabors.ImageSharp.Dithering; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; @@ -11,8 +11,6 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization { - using System.Linq; - public class DitherTests : FileTestBase { public static readonly string[] CommonTestImages = @@ -22,26 +20,29 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization public static readonly TheoryData Ditherers = new TheoryData { - { "Ordered", new OrderedDither() }, - { "Bayer", new BayerDither() } + { "Bayer8x8", KnownDitherers.BayerDither8x8 }, + { "Bayer4x4", KnownDitherers.BayerDither4x4 }, + { "Ordered3x3", KnownDitherers.OrderedDither3x3 }, + { "Bayer2x2", KnownDitherers.BayerDither2x2 } }; public static readonly TheoryData ErrorDiffusers = new TheoryData { - { "Atkinson", new AtkinsonDiffuser() }, - { "Burks", new BurksDiffuser() }, - { "FloydSteinberg", new FloydSteinbergDiffuser() }, - { "JarvisJudiceNinke", new JarvisJudiceNinkeDiffuser() }, - { "Sierra2", new Sierra2Diffuser() }, - { "Sierra3", new Sierra3Diffuser() }, - { "SierraLite", new SierraLiteDiffuser() }, - { "Stucki", new StuckiDiffuser() }, + { "Atkinson", KnownDiffusers.Atkinson }, + { "Burks", KnownDiffusers.Burks }, + { "FloydSteinberg", KnownDiffusers.FloydSteinberg }, + { "JarvisJudiceNinke", KnownDiffusers.JarvisJudiceNinke }, + { "Sierra2", KnownDiffusers.Sierra2 }, + { "Sierra3", KnownDiffusers.Sierra3 }, + { "SierraLite", KnownDiffusers.SierraLite }, + { "StevensonArce", KnownDiffusers.StevensonArce }, + { "Stucki", KnownDiffusers.Stucki }, }; - private static IOrderedDither DefaultDitherer => new OrderedDither(); + private static IOrderedDither DefaultDitherer => KnownDitherers.BayerDither4x4; - private static IErrorDiffuser DefaultErrorDiffuser => new AtkinsonDiffuser(); + private static IErrorDiffuser DefaultErrorDiffuser => KnownDiffusers.Atkinson; [Theory] [WithFileCollection(nameof(CommonTestImages), nameof(Ditherers), DefaultPixelType)] @@ -64,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization { using (Image image = provider.GetImage()) { - image.Mutate(x => x.Dither(diffuser, .5F)); + image.Mutate(x => x.Diffuse(diffuser, .5F)); image.DebugSave(provider, name); } } @@ -80,7 +81,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization image.DebugSave(provider); } } - + [Theory] [WithFile(TestImages.Png.Bike, CommonNonDefaultPixelTypes)] public void DiffusionFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) @@ -88,7 +89,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization { using (Image image = provider.GetImage()) { - image.Mutate(x => x.Dither(DefaultErrorDiffuser, 0.5f)); + image.Mutate(x => x.Diffuse(DefaultErrorDiffuser, 0.5f)); image.DebugSave(provider); } } @@ -120,7 +121,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization { var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); - image.Mutate(x => x.Dither(DefaultErrorDiffuser, .5F, bounds)); + image.Mutate(x => x.Diffuse(DefaultErrorDiffuser, .5F, bounds)); image.DebugSave(provider); ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); diff --git a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs index 70d4df273..24363173a 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs @@ -4,10 +4,18 @@ using System; using System.Collections.Generic; using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSapce; namespace SixLabors.ImageSharp.Tests { - internal struct ApproximateFloatComparer : IEqualityComparer, IEqualityComparer + internal struct ApproximateFloatComparer : + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer { private readonly float Eps; @@ -37,5 +45,52 @@ namespace SixLabors.ImageSharp.Tests { throw new InvalidOperationException(); } + + public bool Equals(CieXyChromaticityCoordinates x, CieXyChromaticityCoordinates y) + { + return this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y); + } + + public int GetHashCode(CieXyChromaticityCoordinates obj) + { + throw new NotImplementedException(); + } + + public bool Equals(RgbPrimariesChromaticityCoordinates x, RgbPrimariesChromaticityCoordinates y) + { + return this.Equals(x.R, y.R) && this.Equals(x.G, y.G) && this.Equals(x.B, y.B); + } + + public int GetHashCode(RgbPrimariesChromaticityCoordinates obj) + { + throw new NotImplementedException(); + } + + public bool Equals(CieXyz x, CieXyz y) + { + return this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y) && this.Equals(x.Z, y.Z); + } + + public int GetHashCode(CieXyz obj) + { + throw new NotImplementedException(); + } + + public bool Equals(IRgbWorkingSpace x, IRgbWorkingSpace y) + { + if (x is IRgbWorkingSpace g1 && y is IRgbWorkingSpace g2) + { + return this.Equals(g1.WhitePoint, g2.WhitePoint) + && this.Equals(g1.ChromaticityCoordinates, g2.ChromaticityCoordinates); + } + + return this.Equals(x.WhitePoint, y.WhitePoint) + && this.Equals(x.ChromaticityCoordinates, y.ChromaticityCoordinates); + } + + public int GetHashCode(IRgbWorkingSpace obj) + { + throw new NotImplementedException(); + } } } \ No newline at end of file