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