Browse Source

Merge branch 'master' into colorspace-transforms

af/merge-core
James Jackson-South 8 years ago
parent
commit
43b514edb7
  1. 4
      .travis.yml
  2. 2
      .vscode/launch.json
  3. 4
      .vscode/tasks.json
  4. 32
      ImageSharp.sln
  5. 12
      samples/AvatarWithRoundedCorner/AvatarWithRoundedCorner.csproj
  6. 111
      samples/AvatarWithRoundedCorner/Program.cs
  7. 3
      samples/AvatarWithRoundedCorner/fb.jpg
  8. 12
      samples/ChangeDefaultEncoderOptions/ChangeDefaultEncoderOptions.csproj
  9. 23
      samples/ChangeDefaultEncoderOptions/Program.cs
  10. 5
      src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs
  11. 4
      src/ImageSharp/ColorSpaces/Conversion/Implementation/LinearRgbAndCieXyzConverterBase.cs
  12. 16
      src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuserBase.cs
  13. 24
      src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs
  14. 56
      src/ImageSharp/Dithering/ErrorDiffusion/KnownDiffusers.cs
  15. 34
      src/ImageSharp/Dithering/ErrorDiffusion/StevensonArceDiffuser.cs
  16. 36
      src/ImageSharp/Dithering/Ordered/BayerDither.cs
  17. 19
      src/ImageSharp/Dithering/Ordered/BayerDither2x2.cs
  18. 19
      src/ImageSharp/Dithering/Ordered/BayerDither4x4.cs
  19. 19
      src/ImageSharp/Dithering/Ordered/BayerDither8x8.cs
  20. 7
      src/ImageSharp/Dithering/Ordered/IOrderedDither.cs
  21. 31
      src/ImageSharp/Dithering/Ordered/KnownDitherers.cs
  22. 50
      src/ImageSharp/Dithering/Ordered/OrderedDither.cs
  23. 19
      src/ImageSharp/Dithering/Ordered/OrderedDither3x3.cs
  24. 53
      src/ImageSharp/Dithering/Ordered/OrderedDitherBase.cs
  25. 94
      src/ImageSharp/Dithering/Ordered/OrderedDitherFactory.cs
  26. 58
      src/ImageSharp/Dithering/error_diffusion.txt
  27. 22
      src/ImageSharp/Memory/Fast2DArray{T}.cs
  28. 9
      src/ImageSharp/MetaData/ImageProperty.cs
  29. 9
      src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs
  30. 4
      src/ImageSharp/MetaData/Profiles/Exif/Rational.cs
  31. 4
      src/ImageSharp/MetaData/Profiles/Exif/SignedRational.cs
  32. 18
      src/ImageSharp/PixelFormats/ColorConstants.cs
  33. 22
      src/ImageSharp/PixelFormats/NamedColors{TPixel}.cs
  34. 86
      src/ImageSharp/Processing/Binarization/BinaryDiffuse.cs
  35. 82
      src/ImageSharp/Processing/Binarization/BinaryDither.cs
  36. 36
      src/ImageSharp/Processing/Binarization/BinaryThreshold.cs
  37. 84
      src/ImageSharp/Processing/Dithering/Diffuse.cs
  38. 58
      src/ImageSharp/Processing/Dithering/Dither.cs
  39. 123
      src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs
  40. 103
      src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor.cs
  41. 73
      src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs
  42. 85
      src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs
  43. 93
      src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs
  44. 113
      src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs
  45. 93
      src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs
  46. 75
      src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessorBase.cs
  47. 49
      src/ImageSharp/Processing/Processors/Dithering/PixelPair.cs
  48. 2
      src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs
  49. 20
      src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs
  50. 2
      src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs
  51. 32
      src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs
  52. 2
      src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs
  53. 2
      src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs
  54. 13
      tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
  55. 4
      tests/ImageSharp.Benchmarks/benchmark.sh
  56. 4
      tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj
  57. 6
      tests/ImageSharp.Tests/Colorspaces/ColorConverterAdaptTest.cs
  58. 6
      tests/ImageSharp.Tests/Colorspaces/RgbAndCieXyzConversionTest.cs
  59. 4
      tests/ImageSharp.Tests/Colorspaces/RgbAndCmykConversionTest.cs
  60. 4
      tests/ImageSharp.Tests/Colorspaces/RgbAndHslConversionTest.cs
  61. 4
      tests/ImageSharp.Tests/Colorspaces/RgbAndHsvConversionTest.cs
  62. 4
      tests/ImageSharp.Tests/Colorspaces/RgbAndYCbCrConversionTest.cs
  63. 65
      tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs
  64. 260
      tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs
  65. 11
      tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs
  66. 32
      tests/ImageSharp.Tests/Image/ImageDiscoverMimeType.cs
  67. 13
      tests/ImageSharp.Tests/Image/ImageLoadTests.cs
  68. 23
      tests/ImageSharp.Tests/Image/ImageSaveTests.cs
  69. 28
      tests/ImageSharp.Tests/Image/MockImageFormatDetector.cs
  70. 10
      tests/ImageSharp.Tests/ImageSharp.Tests.csproj
  71. 12
      tests/ImageSharp.Tests/Memory/Fast2DArrayTests.cs
  72. 3
      tests/ImageSharp.Tests/PixelFormats/ColorDefinitionTests.cs
  73. 6
      tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.cs
  74. 105
      tests/ImageSharp.Tests/Processing/Binarization/BinaryDitherTest.cs
  75. 28
      tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs
  76. 77
      tests/ImageSharp.Tests/Processing/Binarization/DitherTests.cs
  77. 102
      tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs
  78. 104
      tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs
  79. 131
      tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs
  80. 39
      tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs
  81. 57
      tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs

4
.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:

2
.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

4
.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"

32
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

12
samples/AvatarWithRoundedCorner/AvatarWithRoundedCorner.csproj

@ -1,12 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\ImageSharp.Drawing\ImageSharp.Drawing.csproj" />
</ItemGroup>
</Project>

111
samples/AvatarWithRoundedCorner/Program.cs

@ -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<Rgba32> destRound = img.Clone(x => x.ConvertToAvatar(new Size(200, 200), 20)))
{
destRound.Save("output/fb.png");
}
using (Image<Rgba32> destRound = img.Clone(x => x.ConvertToAvatar(new Size(200, 200), 100)))
{
destRound.Save("output/fb-round.png");
}
using (Image<Rgba32> destRound = img.Clone(x => x.ConvertToAvatar(new Size(200, 200), 150)))
{
destRound.Save("output/fb-rounder.png");
}
using (Image<Rgba32> 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<Rgba32>
// 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<Rgba32> ConvertToAvatar(this IImageProcessingContext<Rgba32> 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<Rgba32> CloneAndConvertToAvatarWithoutApply(this Image<Rgba32> image, Size size, float cornerRadius)
{
Image<Rgba32> 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<Rgba32> 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);
}
}
}

3
samples/AvatarWithRoundedCorner/fb.jpg

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:93bb4d6281dc1e845db57e836e0dca30b7a4062e81044efb27ad4d8b1a33130c
size 15787

12
samples/ChangeDefaultEncoderOptions/ChangeDefaultEncoderOptions.csproj

@ -1,12 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\ImageSharp\ImageSharp.csproj" />
</ItemGroup>
</Project>

23
samples/ChangeDefaultEncoderOptions/Program.cs

@ -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
});
}
}
}

5
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);
}
}
}

4
src/ImageSharp/ColorSpaces/Conversion/Implementation/LinearRgbAndCieXyzConverterBase.cs

@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation
internal abstract class LinearRgbAndCieXyzConverterBase
{
/// <summary>
/// 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.
/// </summary>
/// <param name="workingSpace">The Rgb working space.</param>
/// <returns>The <see cref="Matrix4x4"/> based on the chromaticity and working space.</returns>
@ -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
{

16
src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuserBase.cs

@ -70,22 +70,10 @@ namespace SixLabors.ImageSharp.Dithering.Base
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dither<TPixel>(ImageFrame<TPixel> pixels, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY)
public void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY)
where TPixel : struct, IPixel<TPixel>
{
this.Dither(pixels, source, transformed, x, y, minX, minY, maxX, maxY, true);
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY, bool replacePixel)
where TPixel : struct, IPixel<TPixel>
{
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();

24
src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs

@ -6,7 +6,7 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Dithering
{
/// <summary>
/// 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.
/// </summary>
public interface IErrorDiffuser
{
@ -25,25 +25,5 @@ namespace SixLabors.ImageSharp.Dithering
/// <typeparam name="TPixel">The pixel format.</typeparam>
void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY)
where TPixel : struct, IPixel<TPixel>;
/// <summary>
/// Transforms the image applying the dither matrix. This method alters the input pixels array
/// </summary>
/// <param name="image">The image</param>
/// <param name="source">The source pixel</param>
/// <param name="transformed">The transformed pixel</param>
/// <param name="x">The column index.</param>
/// <param name="y">The row index.</param>
/// <param name="minX">The minimum column value.</param>
/// <param name="minY">The minimum row value.</param>
/// <param name="maxX">The maximum column value.</param>
/// <param name="maxY">The maximum row value.</param>
/// <param name="replacePixel">
/// 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.
/// </param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY, bool replacePixel)
where TPixel : struct, IPixel<TPixel>;
}
}
}

56
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
{
/// <summary>
/// Contains reusable static instances of known error diffusion algorithms
/// </summary>
public static class KnownDiffusers
{
/// <summary>
/// Gets the error diffuser that implements the Atkinson algorithm.
/// </summary>
public static IErrorDiffuser Atkinson { get; } = new AtkinsonDiffuser();
/// <summary>
/// Gets the error diffuser that implements the Burks algorithm.
/// </summary>
public static IErrorDiffuser Burks { get; } = new BurksDiffuser();
/// <summary>
/// Gets the error diffuser that implements the Floyd-Steinberg algorithm.
/// </summary>
public static IErrorDiffuser FloydSteinberg { get; } = new FloydSteinbergDiffuser();
/// <summary>
/// Gets the error diffuser that implements the Jarvis-Judice-Ninke algorithm.
/// </summary>
public static IErrorDiffuser JarvisJudiceNinke { get; } = new JarvisJudiceNinkeDiffuser();
/// <summary>
/// Gets the error diffuser that implements the Sierra-2 algorithm.
/// </summary>
public static IErrorDiffuser Sierra2 { get; } = new Sierra2Diffuser();
/// <summary>
/// Gets the error diffuser that implements the Sierra-3 algorithm.
/// </summary>
public static IErrorDiffuser Sierra3 { get; } = new Sierra3Diffuser();
/// <summary>
/// Gets the error diffuser that implements the Sierra-Lite algorithm.
/// </summary>
public static IErrorDiffuser SierraLite { get; } = new SierraLiteDiffuser();
/// <summary>
/// Gets the error diffuser that implements the Stevenson-Arce algorithm.
/// </summary>
public static IErrorDiffuser StevensonArce { get; } = new StevensonArceDiffuser();
/// <summary>
/// Gets the error diffuser that implements the Stucki algorithm.
/// </summary>
public static IErrorDiffuser Stucki { get; } = new StuckiDiffuser();
}
}

34
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
{
/// <summary>
/// Applies error diffusion based dithering using the Stevenson-Arce image dithering algorithm.
/// </summary>
public sealed class StevensonArceDiffuser : ErrorDiffuserBase
{
/// <summary>
/// The diffusion matrix
/// </summary>
private static readonly Fast2DArray<float> 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 }
};
/// <summary>
/// Initializes a new instance of the <see cref="StevensonArceDiffuser"/> class.
/// </summary>
public StevensonArceDiffuser()
: base(StevensonArceMatrix, 200)
{
}
}
}

36
src/ImageSharp/Dithering/Ordered/BayerDither.cs

@ -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
{
/// <summary>
/// Applies error diffusion based dithering using the 4x4 Bayer dithering matrix.
/// <see href="http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT"/>
/// </summary>
public sealed class BayerDither : OrderedDitherBase
{
/// <summary>
/// The threshold matrix.
/// This is calculated by multiplying each value in the original matrix by 16 and subtracting 1
/// </summary>
private static readonly Fast2DArray<byte> ThresholdMatrix =
new byte[,]
{
{ 15, 143, 47, 175 },
{ 207, 79, 239, 111 },
{ 63, 191, 31, 159 },
{ 255, 127, 223, 95 }
};
/// <summary>
/// Initializes a new instance of the <see cref="BayerDither"/> class.
/// </summary>
public BayerDither()
: base(ThresholdMatrix)
{
}
}
}

19
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
{
/// <summary>
/// Applies order dithering using the 2x2 Bayer dithering matrix.
/// </summary>
public sealed class BayerDither2x2 : OrderedDither
{
/// <summary>
/// Initializes a new instance of the <see cref="BayerDither2x2"/> class.
/// </summary>
public BayerDither2x2()
: base(2)
{
}
}
}

19
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
{
/// <summary>
/// Applies order dithering using the 4x4 Bayer dithering matrix.
/// </summary>
public sealed class BayerDither4x4 : OrderedDither
{
/// <summary>
/// Initializes a new instance of the <see cref="BayerDither4x4"/> class.
/// </summary>
public BayerDither4x4()
: base(4)
{
}
}
}

19
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
{
/// <summary>
/// Applies order dithering using the 8x8 Bayer dithering matrix.
/// </summary>
public sealed class BayerDither8x8 : OrderedDither
{
/// <summary>
/// Initializes a new instance of the <see cref="BayerDither8x8"/> class.
/// </summary>
public BayerDither8x8()
: base(8)
{
}
}
}

7
src/ImageSharp/Dithering/Ordered/IOrderedDither.cs

@ -6,7 +6,7 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Dithering
{
/// <summary>
/// Encapsulates properties and methods required to perfom ordered dithering on an image.
/// Encapsulates properties and methods required to perform ordered dithering on an image.
/// </summary>
public interface IOrderedDither
{
@ -17,12 +17,11 @@ namespace SixLabors.ImageSharp.Dithering
/// <param name="source">The source pixel</param>
/// <param name="upper">The color to apply to the pixels above the threshold.</param>
/// <param name="lower">The color to apply to the pixels below the threshold.</param>
/// <param name="rgba">The <see cref="Rgba32"/> to pack/unpack to.</param>
/// <param name="index">The component index to test the threshold against. Must range from 0 to 3.</param>
/// <param name="threshold">The threshold to split the image. Must be between 0 and 1.</param>
/// <param name="x">The column index.</param>
/// <param name="y">The row index.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel upper, TPixel lower, ref Rgba32 rgba, int index, int x, int y)
void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel upper, TPixel lower, float threshold, int x, int y)
where TPixel : struct, IPixel<TPixel>;
}
}

31
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
{
/// <summary>
/// Contains reusable static instances of known ordered dither matrices
/// </summary>
public class KnownDitherers
{
/// <summary>
/// Gets the order ditherer using the 2x2 Bayer dithering matrix
/// </summary>
public static IOrderedDither BayerDither2x2 { get; } = new BayerDither2x2();
/// <summary>
/// Gets the order ditherer using the 3x3 dithering matrix
/// </summary>
public static IOrderedDither OrderedDither3x3 { get; } = new OrderedDither3x3();
/// <summary>
/// Gets the order ditherer using the 4x4 Bayer dithering matrix
/// </summary>
public static IOrderedDither BayerDither4x4 { get; } = new BayerDither4x4();
/// <summary>
/// Gets the order ditherer using the 8x8 Bayer dithering matrix
/// </summary>
public static IOrderedDither BayerDither8x8 { get; } = new BayerDither8x8();
}
}

50
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
{
/// <summary>
/// Applies error diffusion based dithering using the 4x4 ordered dithering matrix.
/// <see href="https://en.wikipedia.org/wiki/Ordered_dithering"/>
/// An ordered dithering matrix with equal sides of arbitrary length
/// </summary>
public sealed class OrderedDither : OrderedDitherBase
public class OrderedDither : IOrderedDither
{
/// <summary>
/// The threshold matrix.
/// This is calculated by multiplying each value in the original matrix by 16
/// </summary>
private static readonly Fast2DArray<byte> ThresholdMatrix =
new byte[,]
{
{ 0, 128, 32, 160 },
{ 192, 64, 224, 96 },
{ 48, 176, 16, 144 },
{ 240, 112, 208, 80 }
};
private readonly Fast2DArray<uint> thresholdMatrix;
private readonly int modulusX;
private readonly int modulusY;
/// <summary>
/// Initializes a new instance of the <see cref="OrderedDither"/> class.
/// </summary>
public OrderedDither()
: base(ThresholdMatrix)
/// <param name="length">The length of the matrix sides</param>
public OrderedDither(uint length)
{
Fast2DArray<uint> 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;
}
/// <inheritdoc />
public void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel upper, TPixel lower, float threshold, int x, int y)
where TPixel : struct, IPixel<TPixel>
{
image[x, y] = this.thresholdMatrix[y % this.modulusY, x % this.modulusX] >= threshold ? lower : upper;
}
}
}

19
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
{
/// <summary>
/// Applies order dithering using the 3x3 dithering matrix.
/// </summary>
public sealed class OrderedDither3x3 : OrderedDither
{
/// <summary>
/// Initializes a new instance of the <see cref="OrderedDither3x3"/> class.
/// </summary>
public OrderedDither3x3()
: base(3)
{
}
}
}

53
src/ImageSharp/Dithering/Ordered/OrderedDitherBase.cs

@ -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
{
/// <summary>
/// The base class for performing ordered dithering using a 4x4 matrix.
/// </summary>
public abstract class OrderedDitherBase : IOrderedDither
{
/// <summary>
/// The dithering matrix
/// </summary>
private Fast2DArray<byte> matrix;
/// <summary>
/// Initializes a new instance of the <see cref="OrderedDitherBase"/> class.
/// </summary>
/// <param name="matrix">The thresholding matrix. </param>
internal OrderedDitherBase(Fast2DArray<byte> matrix)
{
this.matrix = matrix;
}
/// <inheritdoc />
public void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel upper, TPixel lower, ref Rgba32 rgba, int index, int x, int y)
where TPixel : struct, IPixel<TPixel>
{
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.");
}
}
}

94
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
{
/// <summary>
/// A factory for creating ordered dither matrices.
/// </summary>
internal static class OrderedDitherFactory
{
/// <summary>
/// Creates an ordered dithering matrix with equal sides of arbitrary length.
/// <see href="https://en.wikipedia.org/wiki/Ordered_dithering"/>
/// </summary>
/// <param name="length">The length of the matrix sides</param>
/// <returns>The <see cref="Fast2DArray{T}"/></returns>
public static Fast2DArray<uint> 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<uint>((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;
}
}
}

58
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

22
src/ImageSharp/Memory/Fast2DArray{T}.cs

@ -28,6 +28,20 @@ namespace SixLabors.ImageSharp.Memory
/// </summary>
public int Height;
/// <summary>
/// Gets the number of items in the 2D array
/// </summary>
public int Count;
/// <summary>
/// Initializes a new instance of the <see cref="Fast2DArray{T}" /> struct.
/// </summary>
/// <param name="length">The length of each dimension.</param>
public Fast2DArray(int length)
: this(length, length)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Fast2DArray{T}" /> struct.
/// </summary>
@ -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];
}
/// <summary>
@ -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
/// </summary>
/// <param name="data">The source array.</param>
/// <returns>
/// The <see cref="Fast2DArray{T}"/> represenation on the source data.
/// The <see cref="Fast2DArray{T}"/> representation on the source data.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Fast2DArray<T>(T[,] data)

9
src/ImageSharp/MetaData/ImageProperty.cs

@ -71,7 +71,12 @@ namespace SixLabors.ImageSharp.MetaData
/// </returns>
public static bool operator ==(ImageProperty left, ImageProperty right)
{
return Equals(left, right);
if (ReferenceEquals(left, right))
{
return true;
}
return left.Equals(right);
}
/// <summary>
@ -90,7 +95,7 @@ namespace SixLabors.ImageSharp.MetaData
/// </returns>
public static bool operator !=(ImageProperty left, ImageProperty right)
{
return !Equals(left, right);
return !(left == right);
}
/// <summary>

9
src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs

@ -188,7 +188,12 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// </returns>
public static bool operator ==(ExifValue left, ExifValue right)
{
return ExifValue.Equals(left, right);
if (ReferenceEquals(left, right))
{
return true;
}
return left.Equals(right);
}
/// <summary>
@ -205,7 +210,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// </returns>
public static bool operator !=(ExifValue left, ExifValue right)
{
return !ExifValue.Equals(left, right);
return !(left == right);
}
/// <inheritdoc />

4
src/ImageSharp/MetaData/Profiles/Exif/Rational.cs

@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// <returns>The <see cref="bool"/></returns>
public static bool operator ==(Rational left, Rational right)
{
return Rational.Equals(left, right);
return left.Equals(right);
}
/// <summary>
@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// <returns>The <see cref="bool"/></returns>
public static bool operator !=(Rational left, Rational right)
{
return !Rational.Equals(left, right);
return !left.Equals(right);
}
/// <summary>

4
src/ImageSharp/MetaData/Profiles/Exif/SignedRational.cs

@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// <returns>The <see cref="bool"/></returns>
public static bool operator ==(SignedRational left, SignedRational right)
{
return SignedRational.Equals(left, right);
return left.Equals(right);
}
/// <summary>
@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// <returns>The <see cref="bool"/></returns>
public static bool operator !=(SignedRational left, SignedRational right)
{
return !SignedRational.Equals(left, right);
return !left.Equals(right);
}
/// <summary>

18
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
{
/// <summary>
@ -11,23 +8,17 @@ namespace SixLabors.ImageSharp.PixelFormats
/// </summary>
public static class ColorConstants
{
/// <summary>
/// Provides a lazy, one time method of returning the colors.
/// </summary>
private static readonly Lazy<Rgba32[]> SafeColors = new Lazy<Rgba32[]>(GetWebSafeColors);
/// <summary>
/// Gets a collection of named, web safe, colors as defined in the CSS Color Module Level 4.
/// </summary>
public static Rgba32[] WebSafeColors => SafeColors.Value;
public static readonly Rgba32[] WebSafeColors = GetWebSafeColors();
/// <summary>
/// Returns an array of web safe colors.
/// </summary>
/// <returns>The <see cref="T:Color[]"/></returns>
private static Rgba32[] GetWebSafeColors()
{
return new List<Rgba32>
=> new Rgba32[]
{
Rgba32.AliceBlue,
Rgba32.AntiqueWhite,
@ -171,7 +162,6 @@ namespace SixLabors.ImageSharp.PixelFormats
Rgba32.WhiteSmoke,
Rgba32.Yellow,
Rgba32.YellowGreen
}.ToArray();
}
};
}
}
}

22
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
{
/// <summary>
@ -10,6 +12,11 @@ namespace SixLabors.ImageSharp.PixelFormats
public static class NamedColors<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Thread-safe backing field for <see cref="WebSafePalette"/>.
/// </summary>
private static readonly Lazy<TPixel[]> WebSafePaletteLazy = new Lazy<TPixel[]>(GetWebSafePalette, true);
/// <summary>
/// Represents a <see paramref="TPixel"/> matching the W3C definition that has an hex value of #F0F8FF.
/// </summary>
@ -719,5 +726,20 @@ namespace SixLabors.ImageSharp.PixelFormats
/// Represents a <see paramref="TPixel"/> matching the W3C definition that has an hex value of #9ACD32.
/// </summary>
public static readonly TPixel YellowGreen = ColorBuilder<TPixel>.FromRGBA(154, 205, 50, 255);
/// <summary>
/// Gets a <see cref="T:TPixel[]"/> matching the W3C definition of web safe colors.
/// </summary>
public static TPixel[] WebSafePalette => WebSafePaletteLazy.Value;
private static TPixel[] GetWebSafePalette()
{
Rgba32[] constants = ColorConstants.WebSafeColors;
TPixel[] safe = new TPixel[constants.Length + 1];
Span<byte> constantsBytes = constants.AsSpan().NonPortableCast<Rgba32, byte>();
PixelOperations<TPixel>.Instance.PackFromRgba32Bytes(constantsBytes, safe, constants.Length);
return safe;
}
}
}

86
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
{
/// <summary>
/// Extension methods for the <see cref="Image{TPixel}"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Dithers the image reducing it to two colors using error diffusion.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="diffuser">The diffusion algorithm to apply.</param>
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> BinaryDiffuse<TPixel>(this IImageProcessingContext<TPixel> source, IErrorDiffuser diffuser, float threshold)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new BinaryErrorDiffusionProcessor<TPixel>(diffuser, threshold));
return source;
}
/// <summary>
/// Dithers the image reducing it to two colors using error diffusion.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="diffuser">The diffusion algorithm to apply.</param>
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> BinaryDiffuse<TPixel>(this IImageProcessingContext<TPixel> source, IErrorDiffuser diffuser, float threshold, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new BinaryErrorDiffusionProcessor<TPixel>(diffuser, threshold), rectangle);
return source;
}
/// <summary>
/// Dithers the image reducing it to two colors using error diffusion.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="diffuser">The diffusion algorithm to apply.</param>
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <param name="upperColor">The color to use for pixels that are above the threshold.</param>
/// <param name="lowerColor">The color to use for pixels that are below the threshold</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> BinaryDiffuse<TPixel>(this IImageProcessingContext<TPixel> source, IErrorDiffuser diffuser, float threshold, TPixel upperColor, TPixel lowerColor)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new BinaryErrorDiffusionProcessor<TPixel>(diffuser, threshold, upperColor, lowerColor));
return source;
}
/// <summary>
/// Dithers the image reducing it to two colors using error diffusion.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="diffuser">The diffusion algorithm to apply.</param>
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <param name="upperColor">The color to use for pixels that are above the threshold.</param>
/// <param name="lowerColor">The color to use for pixels that are below the threshold</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> BinaryDiffuse<TPixel>(this IImageProcessingContext<TPixel> source, IErrorDiffuser diffuser, float threshold, TPixel upperColor, TPixel lowerColor, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new BinaryErrorDiffusionProcessor<TPixel>(diffuser, threshold, upperColor, lowerColor), rectangle);
return source;
}
}
}

82
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
{
/// <summary>
/// Extension methods for the <see cref="Image{TPixel}"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Dithers the image reducing it to two colors using ordered dithering.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="dither">The ordered ditherer.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> BinaryDither<TPixel>(this IImageProcessingContext<TPixel> source, IOrderedDither dither)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new BinaryOrderedDitherProcessor<TPixel>(dither));
return source;
}
/// <summary>
/// Dithers the image reducing it to two colors using ordered dithering.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="dither">The ordered ditherer.</param>
/// <param name="upperColor">The color to use for pixels that are above the threshold.</param>
/// <param name="lowerColor">The color to use for pixels that are below the threshold</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> BinaryDither<TPixel>(this IImageProcessingContext<TPixel> source, IOrderedDither dither, TPixel upperColor, TPixel lowerColor)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new BinaryOrderedDitherProcessor<TPixel>(dither, upperColor, lowerColor));
return source;
}
/// <summary>
/// Dithers the image reducing it to two colors using ordered dithering.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="dither">The ordered ditherer.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> BinaryDither<TPixel>(this IImageProcessingContext<TPixel> source, IOrderedDither dither, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new BinaryOrderedDitherProcessor<TPixel>(dither), rectangle);
return source;
}
/// <summary>
/// Dithers the image reducing it to two colors using ordered dithering.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="dither">The ordered ditherer.</param>
/// <param name="upperColor">The color to use for pixels that are above the threshold.</param>
/// <param name="lowerColor">The color to use for pixels that are below the threshold</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> BinaryDither<TPixel>(this IImageProcessingContext<TPixel> source, IOrderedDither dither, TPixel upperColor, TPixel lowerColor, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new BinaryOrderedDitherProcessor<TPixel>(dither, upperColor, lowerColor), rectangle);
return source;
}
}
}

36
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<TPixel>(threshold), rectangle);
return source;
}
/// <summary>
/// Applies binarization to the image splitting the pixels at the given threshold.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <param name="upperColor">The color to use for pixels that are above the threshold.</param>
/// <param name="lowerColor">The color to use for pixels that are below the threshold</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> BinaryThreshold<TPixel>(this IImageProcessingContext<TPixel> source, float threshold, TPixel upperColor, TPixel lowerColor)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new BinaryThresholdProcessor<TPixel>(threshold, upperColor, lowerColor));
return source;
}
/// <summary>
/// Applies binarization to the image splitting the pixels at the given threshold.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <param name="upperColor">The color to use for pixels that are above the threshold.</param>
/// <param name="lowerColor">The color to use for pixels that are below the threshold</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> BinaryThreshold<TPixel>(this IImageProcessingContext<TPixel> source, float threshold, TPixel upperColor, TPixel lowerColor, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new BinaryThresholdProcessor<TPixel>(threshold, upperColor, lowerColor), rectangle);
return source;
}
}
}

84
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
{
/// <summary>
/// Extension methods for the <see cref="Image{TPixel}"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Dithers the image reducing it to a web-safe palette using error diffusion.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="diffuser">The diffusion algorithm to apply.</param>
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> Diffuse<TPixel>(this IImageProcessingContext<TPixel> source, IErrorDiffuser diffuser, float threshold)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new ErrorDiffusionPaletteProcessor<TPixel>(diffuser, threshold));
return source;
}
/// <summary>
/// Dithers the image reducing it to a web-safe palette using error diffusion.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="diffuser">The diffusion algorithm to apply.</param>
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> Diffuse<TPixel>(this IImageProcessingContext<TPixel> source, IErrorDiffuser diffuser, float threshold, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new ErrorDiffusionPaletteProcessor<TPixel>(diffuser, threshold), rectangle);
return source;
}
/// <summary>
/// Dithers the image reducing it to the given palette using error diffusion.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="diffuser">The diffusion algorithm to apply.</param>
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <param name="palette">The palette to select substitute colors from.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> Diffuse<TPixel>(this IImageProcessingContext<TPixel> source, IErrorDiffuser diffuser, float threshold, TPixel[] palette)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new ErrorDiffusionPaletteProcessor<TPixel>(diffuser, threshold, palette));
return source;
}
/// <summary>
/// Dithers the image reducing it to the given palette using error diffusion.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="diffuser">The diffusion algorithm to apply.</param>
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <param name="palette">The palette to select substitute colors from.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> Diffuse<TPixel>(this IImageProcessingContext<TPixel> source, IErrorDiffuser diffuser, float threshold, TPixel[] palette, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new ErrorDiffusionPaletteProcessor<TPixel>(diffuser, threshold, palette), rectangle);
return source;
}
}
}

58
src/ImageSharp/Processing/Binarization/Dither.cs → 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
{
/// <summary>
/// Dithers the image reducing it to two colors using ordered dithering.
/// Dithers the image reducing it to a web-safe palette using ordered dithering.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
@ -24,27 +23,27 @@ namespace SixLabors.ImageSharp
public static IImageProcessingContext<TPixel> Dither<TPixel>(this IImageProcessingContext<TPixel> source, IOrderedDither dither)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new OrderedDitherProcessor<TPixel>(dither, 0));
source.ApplyProcessor(new OrderedDitherPaletteProcessor<TPixel>(dither));
return source;
}
/// <summary>
/// Dithers the image reducing it to two colors using ordered dithering.
/// Dithers the image reducing it to the given palette using ordered dithering.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="dither">The ordered ditherer.</param>
/// <param name="index">The component index to test the threshold against. Must range from 0 to 3.</param>
/// <param name="palette">The palette to select substitute colors from.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> Dither<TPixel>(this IImageProcessingContext<TPixel> source, IOrderedDither dither, int index)
public static IImageProcessingContext<TPixel> Dither<TPixel>(this IImageProcessingContext<TPixel> source, IOrderedDither dither, TPixel[] palette)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new OrderedDitherProcessor<TPixel>(dither, index));
source.ApplyProcessor(new OrderedDitherPaletteProcessor<TPixel>(dither, palette));
return source;
}
/// <summary>
/// Dithers the image reducing it to two colors using ordered dithering.
/// Dithers the image reducing it to a web-safe palette using ordered dithering.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
@ -56,58 +55,25 @@ namespace SixLabors.ImageSharp
public static IImageProcessingContext<TPixel> Dither<TPixel>(this IImageProcessingContext<TPixel> source, IOrderedDither dither, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new OrderedDitherProcessor<TPixel>(dither, 0), rectangle);
source.ApplyProcessor(new OrderedDitherPaletteProcessor<TPixel>(dither), rectangle);
return source;
}
/// <summary>
/// Dithers the image reducing it to two colors using ordered dithering.
/// Dithers the image reducing it to the given palette using ordered dithering.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="dither">The ordered ditherer.</param>
/// <param name="palette">The palette to select substitute colors from.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="index">The component index to test the threshold against. Must range from 0 to 3.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> Dither<TPixel>(this IImageProcessingContext<TPixel> source, IOrderedDither dither, Rectangle rectangle, int index)
public static IImageProcessingContext<TPixel> Dither<TPixel>(this IImageProcessingContext<TPixel> source, IOrderedDither dither, TPixel[] palette, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new OrderedDitherProcessor<TPixel>(dither, index), rectangle);
return source;
}
/// <summary>
/// Dithers the image reducing it to two colors using error diffusion.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="diffuser">The diffusion algorithm to apply.</param>
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> Dither<TPixel>(this IImageProcessingContext<TPixel> source, IErrorDiffuser diffuser, float threshold)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new ErrorDiffusionDitherProcessor<TPixel>(diffuser, threshold));
return source;
}
/// <summary>
/// Dithers the image reducing it to two colors using error diffusion.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="diffuser">The diffusion algorithm to apply.</param>
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> Dither<TPixel>(this IImageProcessingContext<TPixel> source, IErrorDiffuser diffuser, float threshold, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new ErrorDiffusionDitherProcessor<TPixel>(diffuser, threshold), rectangle);
source.ApplyProcessor(new OrderedDitherPaletteProcessor<TPixel>(dither, palette), rectangle);
return source;
}
}

123
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
{
/// <summary>
/// Performs binary threshold filtering against an image using error diffusion.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class BinaryErrorDiffusionProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="BinaryErrorDiffusionProcessor{TPixel}"/> class.
/// </summary>
/// <param name="diffuser">The error diffuser</param>
public BinaryErrorDiffusionProcessor(IErrorDiffuser diffuser)
: this(diffuser, .5F)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BinaryErrorDiffusionProcessor{TPixel}"/> class.
/// </summary>
/// <param name="diffuser">The error diffuser</param>
/// <param name="threshold">The threshold to split the image. Must be between 0 and 1.</param>
public BinaryErrorDiffusionProcessor(IErrorDiffuser diffuser, float threshold)
: this(diffuser, threshold, NamedColors<TPixel>.White, NamedColors<TPixel>.Black)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BinaryErrorDiffusionProcessor{TPixel}"/> class.
/// </summary>
/// <param name="diffuser">The error diffuser</param>
/// <param name="threshold">The threshold to split the image. Must be between 0 and 1.</param>
/// <param name="upperColor">The color to use for pixels that are above the threshold.</param>
/// <param name="lowerColor">The color to use for pixels that are below the threshold.</param>
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;
}
/// <summary>
/// Gets the error diffuser.
/// </summary>
public IErrorDiffuser Diffuser { get; }
/// <summary>
/// Gets the threshold value.
/// </summary>
public float Threshold { get; }
/// <summary>
/// Gets the color to use for pixels that are above the threshold.
/// </summary>
public TPixel UpperColor { get; }
/// <summary>
/// Gets the color to use for pixels that fall below the threshold.
/// </summary>
public TPixel LowerColor { get; }
/// <inheritdoc/>
protected override void OnApply(ImageFrame<TPixel> 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<TPixel> 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);
}
}
}
}
}

103
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
{
/// <summary>
/// Performs binary threshold filtering against an image using ordered dithering.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class BinaryOrderedDitherProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="BinaryOrderedDitherProcessor{TPixel}"/> class.
/// </summary>
/// <param name="dither">The ordered ditherer.</param>
public BinaryOrderedDitherProcessor(IOrderedDither dither)
: this(dither, NamedColors<TPixel>.White, NamedColors<TPixel>.Black)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BinaryOrderedDitherProcessor{TPixel}"/> class.
/// </summary>
/// <param name="dither">The ordered ditherer.</param>
/// <param name="upperColor">The color to use for pixels that are above the threshold.</param>
/// <param name="lowerColor">The color to use for pixels that are below the threshold.</param>
public BinaryOrderedDitherProcessor(IOrderedDither dither, TPixel upperColor, TPixel lowerColor)
{
Guard.NotNull(dither, nameof(dither));
this.Dither = dither;
this.UpperColor = upperColor;
this.LowerColor = lowerColor;
}
/// <summary>
/// Gets the ditherer.
/// </summary>
public IOrderedDither Dither { get; }
/// <summary>
/// Gets the color to use for pixels that are above the threshold.
/// </summary>
public TPixel UpperColor { get; }
/// <summary>
/// Gets the color to use for pixels that fall below the threshold.
/// </summary>
public TPixel LowerColor { get; }
/// <inheritdoc/>
protected override void OnApply(ImageFrame<TPixel> 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<TPixel> 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);
}
}
}
}
}

73
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
{
/// <summary>
/// An <see cref="IImageProcessor{TPixel}"/> to perform binary threshold filtering against an
/// <see cref="Image{TPixel}"/>. The image will be converted to grayscale before thresholding occurs.
/// Performs simple binary threshold filtering against an image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class BinaryThresholdProcessor<TPixel> : ImageProcessor<TPixel>
@ -22,14 +22,22 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// </summary>
/// <param name="threshold">The threshold to split the image. Must be between 0 and 1.</param>
public BinaryThresholdProcessor(float threshold)
: this(threshold, NamedColors<TPixel>.White, NamedColors<TPixel>.Black)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BinaryThresholdProcessor{TPixel}"/> class.
/// </summary>
/// <param name="threshold">The threshold to split the image. Must be between 0 and 1.</param>
/// <param name="upperColor">The color to use for pixels that are above the threshold.</param>
/// <param name="lowerColor">The color to use for pixels that are below the threshold.</param>
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<TPixel>.White;
this.LowerColor = NamedColors<TPixel>.Black;
this.UpperColor = upperColor;
this.LowerColor = lowerColor;
}
/// <summary>
@ -47,55 +55,38 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// </summary>
public TPixel LowerColor { get; set; }
/// <inheritdoc/>
protected override void BeforeApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
{
new GrayscaleBt709Processor<TPixel>(1F).Apply(source, sourceRectangle, configuration);
}
/// <inheritdoc/>
protected override void OnApply(ImageFrame<TPixel> 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<TPixel> row = source.GetPixelRowSpan(y - startY);
Span<TPixel> 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;
}
});
}

85
src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs

@ -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
{
/// <summary>
/// An <see cref="IImageProcessor{TPixel}"/> that dithers an image using error diffusion.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class ErrorDiffusionDitherProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="ErrorDiffusionDitherProcessor{TPixel}"/> class.
/// </summary>
/// <param name="diffuser">The error diffuser</param>
/// <param name="threshold">The threshold to split the image. Must be between 0 and 1.</param>
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<TPixel>.White;
this.LowerColor = NamedColors<TPixel>.Black;
}
/// <summary>
/// Gets the error diffuser.
/// </summary>
public IErrorDiffuser Diffuser { get; }
/// <summary>
/// Gets the threshold value.
/// </summary>
public float Threshold { get; }
/// <summary>
/// Gets or sets the color to use for pixels that are above the threshold.
/// </summary>
public TPixel UpperColor { get; set; }
/// <summary>
/// Gets or sets the color to use for pixels that fall below the threshold.
/// </summary>
public TPixel LowerColor { get; set; }
/// <inheritdoc/>
protected override void BeforeApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
{
new GrayscaleBt709Processor<TPixel>(1F).Apply(source, sourceRectangle, configuration);
}
/// <inheritdoc/>
protected override void OnApply(ImageFrame<TPixel> 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<TPixel> 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);
}
}
}
}
}

93
src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs

@ -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
{
/// <summary>
/// An <see cref="IImageProcessor{TPixel}"/> that dithers an image using error diffusion.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class OrderedDitherProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="OrderedDitherProcessor{TPixel}"/> class.
/// </summary>
/// <param name="dither">The ordered ditherer.</param>
/// <param name="index">The component index to test the threshold against. Must range from 0 to 3.</param>
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<TPixel>.White;
this.LowerColor = NamedColors<TPixel>.Black;
}
/// <summary>
/// Gets the ditherer.
/// </summary>
public IOrderedDither Dither { get; }
/// <summary>
/// Gets the component index to test the threshold against.
/// </summary>
public int Index { get; }
/// <summary>
/// Gets or sets the color to use for pixels that are above the threshold.
/// </summary>
public TPixel UpperColor { get; set; }
/// <summary>
/// Gets or sets the color to use for pixels that fall below the threshold.
/// </summary>
public TPixel LowerColor { get; set; }
/// <inheritdoc/>
protected override void BeforeApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
{
new GrayscaleBt709Processor<TPixel>(1F).Apply(source, sourceRectangle, configuration);
}
/// <inheritdoc/>
protected override void OnApply(ImageFrame<TPixel> 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<TPixel> 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);
}
}
}
}
}

113
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
{
/// <summary>
/// An <see cref="IImageProcessor{TPixel}"/> that dithers an image using error diffusion.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class ErrorDiffusionPaletteProcessor<TPixel> : PaletteDitherProcessorBase<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="ErrorDiffusionPaletteProcessor{TPixel}"/> class.
/// </summary>
/// <param name="diffuser">The error diffuser</param>
public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser)
: this(diffuser, .5F)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ErrorDiffusionPaletteProcessor{TPixel}"/> class.
/// </summary>
/// <param name="diffuser">The error diffuser</param>
/// <param name="threshold">The threshold to split the image. Must be between 0 and 1.</param>
public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser, float threshold)
: this(diffuser, threshold, NamedColors<TPixel>.WebSafePalette)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ErrorDiffusionPaletteProcessor{TPixel}"/> class.
/// </summary>
/// <param name="diffuser">The error diffuser</param>
/// <param name="threshold">The threshold to split the image. Must be between 0 and 1.</param>
/// <param name="palette">The palette to select substitute colors from.</param>
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;
}
/// <summary>
/// Gets the error diffuser.
/// </summary>
public IErrorDiffuser Diffuser { get; }
/// <summary>
/// Gets the threshold value.
/// </summary>
public float Threshold { get; }
/// <inheritdoc/>
protected override void OnApply(ImageFrame<TPixel> 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<TPixel> 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<TPixel> 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);
}
}
}
}
}

93
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
{
/// <summary>
/// An <see cref="IImageProcessor{TPixel}"/> 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.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class OrderedDitherPaletteProcessor<TPixel> : PaletteDitherProcessorBase<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="OrderedDitherPaletteProcessor{TPixel}"/> class.
/// </summary>
/// <param name="dither">The ordered ditherer.</param>
public OrderedDitherPaletteProcessor(IOrderedDither dither)
: this(dither, NamedColors<TPixel>.WebSafePalette)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="OrderedDitherPaletteProcessor{TPixel}"/> class.
/// </summary>
/// <param name="dither">The ordered ditherer.</param>
/// <param name="palette">The palette to select substitute colors from.</param>
public OrderedDitherPaletteProcessor(IOrderedDither dither, TPixel[] palette)
: base(palette)
{
Guard.NotNull(dither, nameof(dither));
this.Dither = dither;
}
/// <summary>
/// Gets the ditherer.
/// </summary>
public IOrderedDither Dither { get; }
/// <inheritdoc/>
protected override void OnApply(ImageFrame<TPixel> 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<TPixel> 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<TPixel> 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);
}
}
}
}
}

75
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
{
/// <summary>
/// The base class for dither and diffusion processors that consume a palette.
/// </summary>
internal abstract class PaletteDitherProcessorBase<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly Dictionary<TPixel, PixelPair<TPixel>> cache = new Dictionary<TPixel, PixelPair<TPixel>>();
/// <summary>
/// Initializes a new instance of the <see cref="PaletteDitherProcessorBase{TPixel}"/> class.
/// </summary>
/// <param name="palette">The palette to select substitute colors from.</param>
public PaletteDitherProcessorBase(TPixel[] palette)
{
Guard.NotNull(palette, nameof(palette));
this.Palette = palette;
}
/// <summary>
/// Gets the palette to select substitute colors from.
/// </summary>
public TPixel[] Palette { get; }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected PixelPair<TPixel> 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<TPixel>(closest, secondClosest);
this.cache.Add(pixel, pair);
return pair;
}
}
}

49
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
{
/// <summary>
/// Represents a composite pair of pixels. Used for caching color distance lookups.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal struct PixelPair<TPixel> : IEquatable<PixelPair<TPixel>>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="PixelPair{TPixel}"/> struct.
/// </summary>
/// <param name="first">The first pixel color</param>
/// <param name="second">The second pixel color</param>
public PixelPair(TPixel first, TPixel second)
{
this.First = first;
this.Second = second;
}
/// <summary>
/// Gets the first pixel color
/// </summary>
public TPixel First { get; }
/// <summary>
/// Gets the second pixel color
/// </summary>
public TPixel Second { get; }
/// <inheritdoc/>
public bool Equals(PixelPair<TPixel> other)
=> this.First.Equals(other.First) && this.Second.Equals(other.Second);
/// <inheritdoc/>
public override bool Equals(object obj)
=> obj is PixelPair<TPixel> other && this.First.Equals(other.First) && this.Second.Equals(other.Second);
/// <inheritdoc/>
public override int GetHashCode()
=> HashHelpers.Combine(this.First.GetHashCode(), this.Second.GetHashCode());
}
}

2
src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs

@ -6,7 +6,7 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors
{
/// <summary>
/// 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
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class GrayscaleBt709Processor<TPixel> : FilterProcessor<TPixel>

20
src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs

@ -191,25 +191,5 @@ namespace SixLabors.ImageSharp.Processing.Processors
});
}
}
/// <inheritdoc/>
protected override void AfterImageApply(Image<TPixel> source, Image<TPixel> 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);
}
}
}
}

2
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;

32
src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs

@ -12,6 +12,7 @@ namespace SixLabors.ImageSharp.Quantizers
{
/// <summary>
/// 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.
/// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx"/>
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
@ -31,27 +32,20 @@ namespace SixLabors.ImageSharp.Quantizers
/// <summary>
/// Initializes a new instance of the <see cref="PaletteQuantizer{TPixel}"/> class.
/// </summary>
/// <param name="palette">
/// The color palette. If none is given this will default to the web safe colors defined
/// in the CSS Color Module Level 4.
/// </param>
public PaletteQuantizer()
: this(NamedColors<TPixel>.WebSafePalette)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PaletteQuantizer{TPixel}"/> class.
/// </summary>
/// <param name="palette">The palette to select substitute colors from.</param>
public PaletteQuantizer(TPixel[] palette = null)
: base(true)
{
if (palette == null)
{
Rgba32[] constants = ColorConstants.WebSafeColors;
TPixel[] safe = new TPixel[constants.Length + 1];
Span<byte> constantsBytes = constants.AsSpan().NonPortableCast<Rgba32, byte>();
PixelOperations<TPixel>.Instance.PackFromRgba32Bytes(constantsBytes, safe, constants.Length);
this.colors = safe;
}
else
{
this.colors = palette;
}
Guard.NotNull(palette, nameof(palette));
this.colors = palette;
}
/// <inheritdoc/>
@ -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;

2
src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs

@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Quantizers.Base
public bool Dither { get; set; } = true;
/// <inheritdoc />
public IErrorDiffuser DitherType { get; set; } = new FloydSteinbergDiffuser();
public IErrorDiffuser DitherType { get; set; } = KnownDiffusers.FloydSteinberg;
/// <inheritdoc/>
public virtual QuantizedImage<TPixel> Quantize(ImageFrame<TPixel> image, int maxColors)

2
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;

13
tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp1.1;net461</TargetFrameworks>
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
<OutputType>Exe</OutputType>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<RootNamespace>SixLabors.ImageSharp.Benchmarks</RootNamespace>
@ -17,15 +17,12 @@
<ItemGroup>
<PackageReference Include="Colourful" Version="1.2.1" />
<PackageReference Include="System.Numerics.Vectors" Version="4.4.0" />
<PackageReference Include="BenchmarkDotNet" Version="0.10.12" />
<PackageReference Include="Colourful" Version="1.1.2" />
<PackageReference Include="System.Drawing.Common" Version="4.5.0-preview2-26202-05" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net461'">
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.10.9" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='netcoreapp1.1'">
<PackageReference Include="BenchmarkDotNet" Version="0.10.9" />
<PackageReference Include="System.Reflection" Version="4.3.0" />
<PackageReference Include="CoreCompat.System.Drawing" Version="1.0.0-beta006" />
<PackageReference Include="runtime.linux-x64.CoreCompat.System.Drawing" Version="1.0.0-beta009" />
<PackageReference Include="System.Numerics.Vectors" Version="4.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\ImageSharp.Drawing\ImageSharp.Drawing.csproj" />

4
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
dotnet bin/Release/netcoreapp2.0/ImageSharp.Benchmarks.dll

4
tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj

@ -18,8 +18,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BitMiracle.LibJpeg.NET" Version="1.4.280" />
<PackageReference Include="xunit" Version="2.3.0-beta4-build3742" />
<PackageReference Include="Moq" Version="4.7.99" />
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="Moq" Version="4.8.1" />
<!--<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />-->
<!--<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />-->
</ItemGroup>

6
tests/ImageSharp.Tests/Colorspaces/ColorConverterAdaptTest.cs

@ -19,6 +19,8 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces
{
private static readonly IEqualityComparer<float> 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);

6
tests/ImageSharp.Tests/Colorspaces/RgbAndCieXyzConversionTest.cs

@ -19,6 +19,8 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces
{
private static readonly IEqualityComparer<float> FloatRoundingComparer = new FloatRoundingComparer(6);
private static readonly ApproximateFloatComparer ApproximateComparer = new ApproximateFloatComparer(0.0001F);
/// <summary>
/// Tests conversion from <see cref="CieXyz"/> (<see cref="Illuminants.D50"/>)
/// to <see cref="Rgb"/> (<see cref="Rgb.DefaultWorkingSpace">default sRGB working space</see>).
@ -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);

4
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);
/// <summary>
/// Tests conversion from <see cref="Cmyk"/> to <see cref="Rgb"/>.
/// </summary>
@ -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);

4
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);
/// <summary>
/// Tests conversion from <see cref="Hsl"/> to <see cref="Rgb"/>.
/// </summary>
@ -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);

4
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);
/// <summary>
/// Tests conversion from <see cref="Hsv"/> to <see cref="Rgb"/>.
/// </summary>
@ -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);

4
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);
/// <summary>
/// Tests conversion from <see cref="YCbCr"/> to <see cref="Rgb"/>.
/// </summary>
@ -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);

65
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<IBrush<Rgba32>> brush = new Mock<IBrush<Rgba32>>();
Mock<Region> region = new Mock<Region>();
var brush = new Mock<IBrush<Rgba32>>();
var region = new Mock<Region>();
region.Setup(x => x.Bounds).Returns(bounds);
GraphicsOptions options = new GraphicsOptions(antialias)
var options = new GraphicsOptions(antialias)
{
AntialiasSubpixelDepth = 1
};
FillRegionProcessor<Rgba32> processor = new FillRegionProcessor<Rgba32>(brush.Object, region.Object, options);
Image<Rgba32> img = new Image<Rgba32>(1, 1);
var processor = new FillRegionProcessor<Rgba32>(brush.Object, region.Object, options);
var img = new Image<Rgba32>(1, 1);
processor.Apply(img, bounds);
region.Verify(x => x.Scan(It.IsAny<float>(), It.IsAny<float[]>(), It.IsAny<int>()), 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<IBrush<Rgba32>> brush = new Mock<IBrush<Rgba32>>();
Mock<Region> region = new Mock<Region>();
region.Setup(x => x.Bounds).Returns(bounds);
region.Setup(x => x.MaxIntersections).Returns(10);
region.Setup(x => x.Scan(It.IsAny<float>(), It.IsAny<float[]>(), It.IsAny<int>()))
.Returns<float, Span<float>>((y, span) =>
{
if (y < 5)
{
span[0] = -10f;
span[1] = 100f;
return 2;
}
return 0;
});
GraphicsOptions options = new GraphicsOptions(true)
{
};
FillRegionProcessor<Rgba32> processor = new FillRegionProcessor<Rgba32>(brush.Object, region.Object, options);
Image<Rgba32> img = new Image<Rgba32>(10, 10);
var bounds = new Rectangle(-100, -10, 10, 10);
var brush = new Mock<IBrush<Rgba32>>();
var options = new GraphicsOptions(true);
var processor = new FillRegionProcessor<Rgba32>(brush.Object, new MockRegion(), options);
var img = new Image<Rgba32>(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;
}
}
}
}

260
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<Vector4> 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<JpegColorConverter.ComponentValues, Span<Vector4>, 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<JpegColorConverter.ComponentValues, Span<Vector4>, int> validatePixelValue)
{
ValidateConversion(
converter.ConvertToRGBA,
componentCount,
inputBufferLength,
resultBufferLength,
seed,
validatePixelValue);
}
private static void ValidateConversion(
Action<JpegColorConverter.ComponentValues, Span<Vector4>> doConvert,
int componentCount,
int inputBufferLength,
int resultBufferLength,
int seed,
Action<JpegColorConverter.ComponentValues, Span<Vector4>, 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);
}
}
}

11
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);
}
}
}

32
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<IFileSystem> fileSystem;
private readonly string FilePath;
private readonly Mock<IImageFormatDetector> localMimeTypeDetector;
private readonly IImageFormatDetector localMimeTypeDetector;
private readonly Mock<IImageFormat> 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<IImageFormat>();
this.localMimeTypeDetector = new Mock<IImageFormatDetector>();
this.localMimeTypeDetector.Setup(x => x.HeaderSize).Returns(1);
this.localMimeTypeDetector.Setup(x => x.DetectFormat(It.IsAny<ReadOnlySpan<byte>>())).Returns(localImageFormatMock.Object);
this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormatMock.Object);
this.fileSystem = new Mock<IFileSystem>();
@ -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);
}
}

13
tests/ImageSharp.Tests/Image/ImageLoadTests.cs

@ -14,13 +14,13 @@ namespace SixLabors.ImageSharp.Tests
/// <summary>
/// Tests the <see cref="Image"/> class.
/// </summary>
public class ImageLoadTests : IDisposable
public partial class ImageLoadTests : IDisposable
{
private readonly Mock<IFileSystem> fileSystem;
private Image<Rgba32> returnImage;
private Mock<IImageDecoder> localDecoder;
private readonly string FilePath;
private readonly Mock<IImageFormatDetector> localMimeTypeDetector;
private readonly IImageFormatDetector localMimeTypeDetector;
private readonly Mock<IImageFormat> localImageFormatMock;
public Configuration LocalConfiguration { get; private set; }
@ -35,10 +35,7 @@ namespace SixLabors.ImageSharp.Tests
this.localImageFormatMock = new Mock<IImageFormat>();
this.localDecoder = new Mock<IImageDecoder>();
this.localMimeTypeDetector = new Mock<IImageFormatDetector>();
this.localMimeTypeDetector.Setup(x => x.HeaderSize).Returns(1);
this.localMimeTypeDetector.Setup(x => x.DetectFormat(It.IsAny<ReadOnlySpan<byte>>())).Returns(localImageFormatMock.Object);
this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormatMock.Object);
this.localDecoder.Setup(x => x.Decode<Rgba32>(It.IsAny<Configuration>(), It.IsAny<Stream>()))
.Callback<Configuration, Stream>((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();

23
tests/ImageSharp.Tests/Image/ImageSaveTests.cs

@ -24,17 +24,14 @@ namespace SixLabors.ImageSharp.Tests
private readonly Mock<IFileSystem> fileSystem;
private readonly Mock<IImageEncoder> encoder;
private readonly Mock<IImageEncoder> encoderNotInFormat;
private Mock<IImageFormatDetector> localMimeTypeDetector;
private IImageFormatDetector localMimeTypeDetector;
private Mock<IImageFormat> localImageFormat;
public ImageSaveTests()
{
this.localImageFormat = new Mock<IImageFormat>();
this.localImageFormat.Setup(x => x.FileExtensions).Returns(new[] { "png" });
this.localMimeTypeDetector = new Mock<IImageFormatDetector>();
this.localMimeTypeDetector.Setup(x => x.HeaderSize).Returns(1);
this.localMimeTypeDetector.Setup(x => x.DetectFormat(It.IsAny<Span<byte>>())).Returns(localImageFormat.Object);
this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormat.Object);
this.encoder = new Mock<IImageEncoder>();
@ -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<Rgba32>(config, 1, 1);
}
@ -57,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests
{
using (Image<TPixel> 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<TPixel> image = provider.GetImage())
{
byte[] buffer = new byte[image.Width*image.Height*Unsafe.SizeOf<TPixel>()];
byte[] buffer = new byte[image.Width * image.Height * Unsafe.SizeOf<TPixel>()];
image.SavePixelData(buffer);
image.ComparePixelBufferTo(buffer.AsSpan().NonPortableCast<byte, TPixel>());
}
}
[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<ArgumentOutOfRangeException>(() =>
{
@ -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<Rgba32>(this.Image, It.IsAny<Stream>()));
}
@ -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<Rgba32>(this.Image, stream));
}

28
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
{
/// <summary>
/// You can't mock the "DetectFormat" method due to the ReadOnlySpan{byte} parameter.
/// </summary>
public class MockImageFormatDetector : IImageFormatDetector
{
private IImageFormat localImageFormatMock;
public MockImageFormatDetector(IImageFormat imageFormat)
{
this.localImageFormatMock = imageFormat;
}
public int HeaderSize => 1;
public IImageFormat DetectFormat(ReadOnlySpan<byte> header)
{
return this.localImageFormatMock;
}
}
}

10
tests/ImageSharp.Tests/ImageSharp.Tests.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
<TargetFramework>netcoreapp2.0</TargetFramework>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<DebugType Condition="$(codecov) != ''">full</DebugType>
<DebugType Condition="$(codecov) == ''">portable</DebugType>
@ -16,14 +16,14 @@
<None Include="PixelFormats\PixelOperationsTests.Blender.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CoreCompat.System.Drawing" Version="1.0.0-beta006" />
<PackageReference Include="System.Drawing.Common" Version="4.5.0-preview2-26202-05" />
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
<PackageReference Include="Moq" Version="4.7.145" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />
<PackageReference Include="xunit" Version="2.3.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.0" />
<PackageReference Include="Moq" Version="4.7.137" />
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
<PackageReference Include="Moq" Version="4.8.1" />
<!--<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta001">
<PrivateAssets>All</PrivateAssets>
</PackageReference>-->

12
tests/ImageSharp.Tests/Memory/Fast2DArrayTests.cs

@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
{
Assert.Throws<ArgumentNullException>(() =>
{
Fast2DArray<float> fast = new Fast2DArray<float>(null);
var fast = new Fast2DArray<float>(null);
});
}
@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
{
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
Fast2DArray<float> fast = new Fast2DArray<float>(0, 10);
var fast = new Fast2DArray<float>(0, 10);
});
}
@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
{
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
Fast2DArray<float> fast = new Fast2DArray<float>(10, 0);
var fast = new Fast2DArray<float>(10, 0);
});
}
@ -49,14 +49,14 @@ namespace SixLabors.ImageSharp.Tests.Memory
{
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
Fast2DArray<float> fast = new Fast2DArray<float>(new float[0, 0]);
var fast = new Fast2DArray<float>(new float[0, 0]);
});
}
[Fact]
public void Fast2DArrayReturnsCorrectDimensions()
{
Fast2DArray<float> fast = new Fast2DArray<float>(FloydSteinbergMatrix);
var fast = new Fast2DArray<float>(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<float> fast = new Fast2DArray<float>(4, 4);
var fast = new Fast2DArray<float>(4, 4);
const float Val = 5F;
fast[3, 3] = Val;

3
tests/ImageSharp.Tests/PixelFormats/ColorDefinitionTests.cs

@ -16,7 +16,8 @@ namespace SixLabors.ImageSharp.Tests
get
{
var result = new TheoryData<string>();
foreach (string name in typeof(NamedColors<Rgba32>).GetTypeInfo().GetFields().Select(x => x.Name ))
foreach (string name in typeof(NamedColors<Rgba32>).GetTypeInfo()
.GetFields().Where(x => x.Name != nameof(NamedColors<Rgba32>.WebSafePalette)).Select(x => x.Name))
{
result.Add(name);
}

6
tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.cs

@ -387,13 +387,13 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats
internal static void TestOperation<TSource, TDest>(
TSource[] source,
TDest[] expected,
Action<Span<TSource>, Buffer<TDest>> action)
Action<Buffer<TSource>, Buffer<TDest>> action)
where TSource : struct
where TDest : struct
{
using (TestBuffers<TSource, TDest> buffers = new TestBuffers<TSource, TDest>(source, expected))
using (var buffers = new TestBuffers<TSource, TDest>(source, expected))
{
action(buffers.Source, buffers.ActualDestBuffer);
action(buffers.SourceBuffer, buffers.ActualDestBuffer);
buffers.Verify();
}
}

105
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<Rgba32> p = this.Verify<BinaryOrderedDitherProcessor<Rgba32>>();
Assert.Equal(this.orderedDither, p.Dither);
Assert.Equal(NamedColors<Rgba32>.White, p.UpperColor);
Assert.Equal(NamedColors<Rgba32>.Black, p.LowerColor);
}
[Fact]
public void BinaryDither_rect_CorrectProcessor()
{
this.operations.BinaryDither(this.orderedDither, this.rect);
BinaryOrderedDitherProcessor<Rgba32> p = this.Verify<BinaryOrderedDitherProcessor<Rgba32>>(this.rect);
Assert.Equal(this.orderedDither, p.Dither);
Assert.Equal(NamedColors<Rgba32>.White, p.UpperColor);
Assert.Equal(NamedColors<Rgba32>.Black, p.LowerColor);
}
[Fact]
public void BinaryDither_index_CorrectProcessor()
{
this.operations.BinaryDither(this.orderedDither, NamedColors<Rgba32>.Yellow, NamedColors<Rgba32>.HotPink);
BinaryOrderedDitherProcessor<Rgba32> p = this.Verify<BinaryOrderedDitherProcessor<Rgba32>>();
Assert.Equal(this.orderedDither, p.Dither);
Assert.Equal(NamedColors<Rgba32>.Yellow, p.UpperColor);
Assert.Equal(NamedColors<Rgba32>.HotPink, p.LowerColor);
}
[Fact]
public void BinaryDither_index_rect_CorrectProcessor()
{
this.operations.BinaryDither(this.orderedDither, NamedColors<Rgba32>.Yellow, NamedColors<Rgba32>.HotPink, this.rect);
BinaryOrderedDitherProcessor<Rgba32> p = this.Verify<BinaryOrderedDitherProcessor<Rgba32>>(this.rect);
Assert.Equal(this.orderedDither, p.Dither);
Assert.Equal(NamedColors<Rgba32>.HotPink, p.LowerColor);
}
[Fact]
public void BinaryDither_ErrorDiffuser_CorrectProcessor()
{
this.operations.BinaryDiffuse(this.errorDiffuser, .4F);
BinaryErrorDiffusionProcessor<Rgba32> p = this.Verify<BinaryErrorDiffusionProcessor<Rgba32>>();
Assert.Equal(this.errorDiffuser, p.Diffuser);
Assert.Equal(.4F, p.Threshold);
Assert.Equal(NamedColors<Rgba32>.White, p.UpperColor);
Assert.Equal(NamedColors<Rgba32>.Black, p.LowerColor);
}
[Fact]
public void BinaryDither_ErrorDiffuser_rect_CorrectProcessor()
{
this.operations.BinaryDiffuse(this.errorDiffuser, .3F, this.rect);
BinaryErrorDiffusionProcessor<Rgba32> p = this.Verify<BinaryErrorDiffusionProcessor<Rgba32>>(this.rect);
Assert.Equal(this.errorDiffuser, p.Diffuser);
Assert.Equal(.3F, p.Threshold);
Assert.Equal(NamedColors<Rgba32>.White, p.UpperColor);
Assert.Equal(NamedColors<Rgba32>.Black, p.LowerColor);
}
[Fact]
public void BinaryDither_ErrorDiffuser_CorrectProcessorWithColors()
{
this.operations.BinaryDiffuse(this.errorDiffuser, .5F, NamedColors<Rgba32>.HotPink, NamedColors<Rgba32>.Yellow);
BinaryErrorDiffusionProcessor<Rgba32> p = this.Verify<BinaryErrorDiffusionProcessor<Rgba32>>();
Assert.Equal(this.errorDiffuser, p.Diffuser);
Assert.Equal(.5F, p.Threshold);
Assert.Equal(NamedColors<Rgba32>.HotPink, p.UpperColor);
Assert.Equal(NamedColors<Rgba32>.Yellow, p.LowerColor);
}
[Fact]
public void BinaryDither_ErrorDiffuser_rect_CorrectProcessorWithColors()
{
this.operations.BinaryDiffuse(this.errorDiffuser, .5F, NamedColors<Rgba32>.HotPink, NamedColors<Rgba32>.Yellow, this.rect);
BinaryErrorDiffusionProcessor<Rgba32> p = this.Verify<BinaryErrorDiffusionProcessor<Rgba32>>(this.rect);
Assert.Equal(this.errorDiffuser, p.Diffuser);
Assert.Equal(.5F, p.Threshold);
Assert.Equal(NamedColors<Rgba32>.HotPink, p.UpperColor);
Assert.Equal(NamedColors<Rgba32>.Yellow, p.LowerColor);
}
}
}

28
tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs

@ -15,16 +15,40 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization
public void BinaryThreshold_CorrectProcessor()
{
this.operations.BinaryThreshold(.23f);
var p = this.Verify<BinaryThresholdProcessor<Rgba32>>();
BinaryThresholdProcessor<Rgba32> p = this.Verify<BinaryThresholdProcessor<Rgba32>>();
Assert.Equal(.23f, p.Threshold);
Assert.Equal(NamedColors<Rgba32>.White, p.UpperColor);
Assert.Equal(NamedColors<Rgba32>.Black, p.LowerColor);
}
[Fact]
public void BinaryThreshold_rect_CorrectProcessor()
{
this.operations.BinaryThreshold(.93f, this.rect);
var p = this.Verify<BinaryThresholdProcessor<Rgba32>>(this.rect);
BinaryThresholdProcessor<Rgba32> p = this.Verify<BinaryThresholdProcessor<Rgba32>>(this.rect);
Assert.Equal(.93f, p.Threshold);
Assert.Equal(NamedColors<Rgba32>.White, p.UpperColor);
Assert.Equal(NamedColors<Rgba32>.Black, p.LowerColor);
}
[Fact]
public void BinaryThreshold_CorrectProcessorWithUpperLower()
{
this.operations.BinaryThreshold(.23f, NamedColors<Rgba32>.HotPink, NamedColors<Rgba32>.Yellow);
BinaryThresholdProcessor<Rgba32> p = this.Verify<BinaryThresholdProcessor<Rgba32>>();
Assert.Equal(.23f, p.Threshold);
Assert.Equal(NamedColors<Rgba32>.HotPink, p.UpperColor);
Assert.Equal(NamedColors<Rgba32>.Yellow, p.LowerColor);
}
[Fact]
public void BinaryThreshold_rect_CorrectProcessorWithUpperLower()
{
this.operations.BinaryThreshold(.93f, NamedColors<Rgba32>.HotPink, NamedColors<Rgba32>.Yellow, this.rect);
BinaryThresholdProcessor<Rgba32> p = this.Verify<BinaryThresholdProcessor<Rgba32>>(this.rect);
Assert.Equal(.93f, p.Threshold);
Assert.Equal(NamedColors<Rgba32>.HotPink, p.UpperColor);
Assert.Equal(NamedColors<Rgba32>.Yellow, p.LowerColor);
}
}
}

77
tests/ImageSharp.Tests/Processing/Binarization/DitherTests.cs

@ -1,77 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Dithering;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors;
using Moq;
using SixLabors.Primitives;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Binarization
{
public class DitherTest : BaseImageOperationsExtensionTest
{
private readonly IOrderedDither orderedDither;
private readonly IErrorDiffuser errorDiffuser;
public DitherTest()
{
this.orderedDither = new Mock<IOrderedDither>().Object;
this.errorDiffuser = new Mock<IErrorDiffuser>().Object;
}
[Fact]
public void Dither_CorrectProcessor()
{
this.operations.Dither(orderedDither);
var p = this.Verify<OrderedDitherProcessor<Rgba32>>();
Assert.Equal(this.orderedDither, p.Dither);
Assert.Equal(0, p.Index);
}
[Fact]
public void Dither_rect_CorrectProcessor()
{
this.operations.Dither(orderedDither, this.rect);
var p = this.Verify<OrderedDitherProcessor<Rgba32>>(this.rect);
Assert.Equal(this.orderedDither, p.Dither);
Assert.Equal(0, p.Index);
}
[Fact]
public void Dither_index_CorrectProcessor()
{
this.operations.Dither(orderedDither, 2);
var p = this.Verify<OrderedDitherProcessor<Rgba32>>();
Assert.Equal(this.orderedDither, p.Dither);
Assert.Equal(2, p.Index);
}
[Fact]
public void Dither_index_rect_CorrectProcessor()
{
this.operations.Dither(orderedDither, this.rect, 2);
var p = this.Verify<OrderedDitherProcessor<Rgba32>>(this.rect);
Assert.Equal(this.orderedDither, p.Dither);
Assert.Equal(2, p.Index);
}
[Fact]
public void Dither_ErrorDifuser_CorrectProcessor()
{
this.operations.Dither(errorDiffuser, 4);
var p = this.Verify<ErrorDiffusionDitherProcessor<Rgba32>>();
Assert.Equal(this.errorDiffuser, p.Diffuser);
Assert.Equal(4, p.Threshold);
}
[Fact]
public void Dither_ErrorDifuser_rect_CorrectProcessor()
{
this.operations.Dither(this.errorDiffuser, 3, this.rect);
var p = this.Verify<ErrorDiffusionDitherProcessor<Rgba32>>(this.rect);
Assert.Equal(this.errorDiffuser, p.Diffuser);
Assert.Equal(3, p.Threshold);
}
}
}

102
tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs

@ -0,0 +1,102 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Dithering;
using SixLabors.ImageSharp.Memory;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Binarization
{
public class OrderedDitherFactoryTests
{
private static readonly Fast2DArray<uint> Expected2x2Matrix = new Fast2DArray<uint>(
new uint[2, 2]
{
{ 0, 2 },
{ 3, 1 }
});
private static readonly Fast2DArray<uint> Expected3x3Matrix = new Fast2DArray<uint>(
new uint[3, 3]
{
{ 0, 5, 2 },
{ 7, 4, 8 },
{ 3, 6, 1 }
});
private static readonly Fast2DArray<uint> Expected4x4Matrix = new Fast2DArray<uint>(
new uint[4, 4]
{
{ 0, 8, 2, 10 },
{ 12, 4, 14, 6 },
{ 3, 11, 1, 9 },
{ 15, 7, 13, 5 }
});
private static readonly Fast2DArray<uint> Expected8x8Matrix = new Fast2DArray<uint>(
new uint[8, 8]
{
{ 0, 32, 8, 40, 2, 34, 10, 42 },
{ 48, 16, 56, 24, 50, 18, 58, 26 },
{ 12, 44, 4, 36, 14, 46, 6, 38 },
{ 60, 28, 52, 20, 62, 30, 54, 22 },
{ 3, 35, 11, 43, 1, 33, 9, 41 },
{ 51, 19, 59, 27, 49, 17, 57, 25 },
{ 15, 47, 7, 39, 13, 45, 5, 37 },
{ 63, 31, 55, 23, 61, 29, 53, 21 }
});
[Fact]
public void OrderedDitherFactoryCreatesCorrect2x2Matrix()
{
Fast2DArray<uint> actual = OrderedDitherFactory.CreateDitherMatrix(2);
for (int y = 0; y < actual.Height; y++)
{
for (int x = 0; x < actual.Width; x++)
{
Assert.Equal(Expected2x2Matrix[y, x], actual[y, x]);
}
}
}
[Fact]
public void OrderedDitherFactoryCreatesCorrect3x3Matrix()
{
Fast2DArray<uint> actual = OrderedDitherFactory.CreateDitherMatrix(3);
for (int y = 0; y < actual.Height; y++)
{
for (int x = 0; x < actual.Width; x++)
{
Assert.Equal(Expected3x3Matrix[y, x], actual[y, x]);
}
}
}
[Fact]
public void OrderedDitherFactoryCreatesCorrect4x4Matrix()
{
Fast2DArray<uint> actual = OrderedDitherFactory.CreateDitherMatrix(4);
for (int y = 0; y < actual.Height; y++)
{
for (int x = 0; x < actual.Width; x++)
{
Assert.Equal(Expected4x4Matrix[y, x], actual[y, x]);
}
}
}
[Fact]
public void OrderedDitherFactoryCreatesCorrect8x8Matrix()
{
Fast2DArray<uint> actual = OrderedDitherFactory.CreateDitherMatrix(8);
for (int y = 0; y < actual.Height; y++)
{
for (int x = 0; x < actual.Width; x++)
{
Assert.Equal(Expected8x8Matrix[y, x], actual[y, x]);
}
}
}
}
}

104
tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs

@ -0,0 +1,104 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Dithering;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Binarization
{
public class DitherTest : BaseImageOperationsExtensionTest
{
private readonly IOrderedDither orderedDither;
private readonly IErrorDiffuser errorDiffuser;
private readonly Rgba32[] TestPalette =
{
Rgba32.Red,
Rgba32.Green,
Rgba32.Blue
};
public DitherTest()
{
this.orderedDither = KnownDitherers.BayerDither4x4;
this.errorDiffuser = KnownDiffusers.FloydSteinberg;
}
[Fact]
public void Dither_CorrectProcessor()
{
this.operations.Dither(this.orderedDither);
OrderedDitherPaletteProcessor<Rgba32> p = this.Verify<OrderedDitherPaletteProcessor<Rgba32>>();
Assert.Equal(this.orderedDither, p.Dither);
Assert.Equal(NamedColors<Rgba32>.WebSafePalette, p.Palette);
}
[Fact]
public void Dither_rect_CorrectProcessor()
{
this.operations.Dither(this.orderedDither, this.rect);
OrderedDitherPaletteProcessor<Rgba32> p = this.Verify<OrderedDitherPaletteProcessor<Rgba32>>(this.rect);
Assert.Equal(this.orderedDither, p.Dither);
Assert.Equal(NamedColors<Rgba32>.WebSafePalette, p.Palette);
}
[Fact]
public void Dither_index_CorrectProcessor()
{
this.operations.Dither(this.orderedDither, this.TestPalette);
OrderedDitherPaletteProcessor<Rgba32> p = this.Verify<OrderedDitherPaletteProcessor<Rgba32>>();
Assert.Equal(this.orderedDither, p.Dither);
Assert.Equal(this.TestPalette, p.Palette);
}
[Fact]
public void Dither_index_rect_CorrectProcessor()
{
this.operations.Dither(this.orderedDither, this.TestPalette, this.rect);
OrderedDitherPaletteProcessor<Rgba32> p = this.Verify<OrderedDitherPaletteProcessor<Rgba32>>(this.rect);
Assert.Equal(this.orderedDither, p.Dither);
Assert.Equal(this.TestPalette, p.Palette);
}
[Fact]
public void Dither_ErrorDiffuser_CorrectProcessor()
{
this.operations.Diffuse(this.errorDiffuser, .4F);
ErrorDiffusionPaletteProcessor<Rgba32> p = this.Verify<ErrorDiffusionPaletteProcessor<Rgba32>>();
Assert.Equal(this.errorDiffuser, p.Diffuser);
Assert.Equal(.4F, p.Threshold);
Assert.Equal(NamedColors<Rgba32>.WebSafePalette, p.Palette);
}
[Fact]
public void Dither_ErrorDiffuser_rect_CorrectProcessor()
{
this.operations.Diffuse(this.errorDiffuser, .3F, this.rect);
ErrorDiffusionPaletteProcessor<Rgba32> p = this.Verify<ErrorDiffusionPaletteProcessor<Rgba32>>(this.rect);
Assert.Equal(this.errorDiffuser, p.Diffuser);
Assert.Equal(.3F, p.Threshold);
Assert.Equal(NamedColors<Rgba32>.WebSafePalette, p.Palette);
}
[Fact]
public void Dither_ErrorDiffuser_CorrectProcessorWithColors()
{
this.operations.Diffuse(this.errorDiffuser, .5F, this.TestPalette);
ErrorDiffusionPaletteProcessor<Rgba32> p = this.Verify<ErrorDiffusionPaletteProcessor<Rgba32>>();
Assert.Equal(this.errorDiffuser, p.Diffuser);
Assert.Equal(.5F, p.Threshold);
Assert.Equal(this.TestPalette, p.Palette);
}
[Fact]
public void Dither_ErrorDiffuser_rect_CorrectProcessorWithColors()
{
this.operations.Diffuse(this.errorDiffuser, .5F, this.TestPalette, this.rect);
ErrorDiffusionPaletteProcessor<Rgba32> p = this.Verify<ErrorDiffusionPaletteProcessor<Rgba32>>(this.rect);
Assert.Equal(this.errorDiffuser, p.Diffuser);
Assert.Equal(.5F, p.Threshold);
Assert.Equal(this.TestPalette, p.Palette);
}
}
}

131
tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs

@ -0,0 +1,131 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Dithering;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.Primitives;
using Xunit;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
{
public class BinaryDitherTests : FileTestBase
{
public static readonly string[] CommonTestImages =
{
TestImages.Png.CalliphoraPartial, TestImages.Png.Bike
};
public static readonly TheoryData<string, IOrderedDither> Ditherers = new TheoryData<string, IOrderedDither>
{
{ "Bayer8x8", KnownDitherers.BayerDither8x8 },
{ "Bayer4x4", KnownDitherers.BayerDither4x4 },
{ "Ordered3x3", KnownDitherers.OrderedDither3x3 },
{ "Bayer2x2", KnownDitherers.BayerDither2x2 }
};
public static readonly TheoryData<string, IErrorDiffuser> ErrorDiffusers = new TheoryData<string, IErrorDiffuser>
{
{ "Atkinson", KnownDiffusers.Atkinson },
{ "Burks", KnownDiffusers.Burks },
{ "FloydSteinberg", KnownDiffusers.FloydSteinberg },
{ "JarvisJudiceNinke", KnownDiffusers.JarvisJudiceNinke },
{ "Sierra2", KnownDiffusers.Sierra2 },
{ "Sierra3", KnownDiffusers.Sierra3 },
{ "SierraLite", KnownDiffusers.SierraLite },
{ "StevensonArce", KnownDiffusers.StevensonArce },
{ "Stucki", KnownDiffusers.Stucki },
};
private static IOrderedDither DefaultDitherer => KnownDitherers.BayerDither4x4;
private static IErrorDiffuser DefaultErrorDiffuser => KnownDiffusers.Atkinson;
[Theory]
[WithFileCollection(nameof(CommonTestImages), nameof(Ditherers), DefaultPixelType)]
[WithTestPatternImages(nameof(Ditherers), 100, 100, DefaultPixelType)]
public void BinaryDitherFilter_WorksWithAllDitherers<TPixel>(TestImageProvider<TPixel> provider, string name, IOrderedDither ditherer)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(x => x.BinaryDither(ditherer));
image.DebugSave(provider, name);
}
}
[Theory]
[WithFileCollection(nameof(CommonTestImages), nameof(ErrorDiffusers), DefaultPixelType)]
[WithTestPatternImages(nameof(ErrorDiffusers), 100, 100, DefaultPixelType)]
public void DiffusionFilter_WorksWithAllErrorDiffusers<TPixel>(TestImageProvider<TPixel> provider, string name, IErrorDiffuser diffuser)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(x => x.BinaryDiffuse(diffuser, .5F));
image.DebugSave(provider, name);
}
}
[Theory]
[WithFile(TestImages.Png.Bike, CommonNonDefaultPixelTypes)]
public void BinaryDitherFilter_ShouldNotDependOnSinglePixelType<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(x => x.BinaryDither(DefaultDitherer));
image.DebugSave(provider);
}
}
[Theory]
[WithFile(TestImages.Png.Bike, CommonNonDefaultPixelTypes)]
public void DiffusionFilter_ShouldNotDependOnSinglePixelType<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(x => x.BinaryDiffuse(DefaultErrorDiffuser, 0.5f));
image.DebugSave(provider);
}
}
[Theory]
[WithFile(TestImages.Png.CalliphoraPartial, DefaultPixelType)]
public void ApplyDitherFilterInBox<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> source = provider.GetImage())
using (Image<TPixel> image = source.Clone())
{
var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2);
image.Mutate(x => x.BinaryDither(DefaultDitherer, bounds));
image.DebugSave(provider);
ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds);
}
}
[Theory]
[WithFile(TestImages.Png.CalliphoraPartial, DefaultPixelType)]
public void ApplyDiffusionFilterInBox<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> source = provider.GetImage())
using (Image<TPixel> image = source.Clone())
{
var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2);
image.Mutate(x => x.BinaryDiffuse(DefaultErrorDiffuser, .5F, bounds));
image.DebugSave(provider);
ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds);
}
}
}
}

39
tests/ImageSharp.Tests/Processing/Processors/Binarization/DitherTests.cs → tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs

@ -1,7 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Dithering;
using SixLabors.ImageSharp.Dithering;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
@ -11,8 +11,6 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
{
using System.Linq;
public class DitherTests : FileTestBase
{
public static readonly string[] CommonTestImages =
@ -22,26 +20,29 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
public static readonly TheoryData<string, IOrderedDither> Ditherers = new TheoryData<string, IOrderedDither>
{
{ "Ordered", new OrderedDither() },
{ "Bayer", new BayerDither() }
{ "Bayer8x8", KnownDitherers.BayerDither8x8 },
{ "Bayer4x4", KnownDitherers.BayerDither4x4 },
{ "Ordered3x3", KnownDitherers.OrderedDither3x3 },
{ "Bayer2x2", KnownDitherers.BayerDither2x2 }
};
public static readonly TheoryData<string, IErrorDiffuser> ErrorDiffusers = new TheoryData<string, IErrorDiffuser>
{
{ "Atkinson", new AtkinsonDiffuser() },
{ "Burks", new BurksDiffuser() },
{ "FloydSteinberg", new FloydSteinbergDiffuser() },
{ "JarvisJudiceNinke", new JarvisJudiceNinkeDiffuser() },
{ "Sierra2", new Sierra2Diffuser() },
{ "Sierra3", new Sierra3Diffuser() },
{ "SierraLite", new SierraLiteDiffuser() },
{ "Stucki", new StuckiDiffuser() },
{ "Atkinson", KnownDiffusers.Atkinson },
{ "Burks", KnownDiffusers.Burks },
{ "FloydSteinberg", KnownDiffusers.FloydSteinberg },
{ "JarvisJudiceNinke", KnownDiffusers.JarvisJudiceNinke },
{ "Sierra2", KnownDiffusers.Sierra2 },
{ "Sierra3", KnownDiffusers.Sierra3 },
{ "SierraLite", KnownDiffusers.SierraLite },
{ "StevensonArce", KnownDiffusers.StevensonArce },
{ "Stucki", KnownDiffusers.Stucki },
};
private static IOrderedDither DefaultDitherer => new OrderedDither();
private static IOrderedDither DefaultDitherer => KnownDitherers.BayerDither4x4;
private static IErrorDiffuser DefaultErrorDiffuser => new AtkinsonDiffuser();
private static IErrorDiffuser DefaultErrorDiffuser => KnownDiffusers.Atkinson;
[Theory]
[WithFileCollection(nameof(CommonTestImages), nameof(Ditherers), DefaultPixelType)]
@ -64,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
{
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(x => x.Dither(diffuser, .5F));
image.Mutate(x => x.Diffuse(diffuser, .5F));
image.DebugSave(provider, name);
}
}
@ -80,7 +81,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
image.DebugSave(provider);
}
}
[Theory]
[WithFile(TestImages.Png.Bike, CommonNonDefaultPixelTypes)]
public void DiffusionFilter_ShouldNotDependOnSinglePixelType<TPixel>(TestImageProvider<TPixel> provider)
@ -88,7 +89,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
{
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(x => x.Dither(DefaultErrorDiffuser, 0.5f));
image.Mutate(x => x.Diffuse(DefaultErrorDiffuser, 0.5f));
image.DebugSave(provider);
}
}
@ -120,7 +121,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
{
var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2);
image.Mutate(x => x.Dither(DefaultErrorDiffuser, .5F, bounds));
image.Mutate(x => x.Diffuse(DefaultErrorDiffuser, .5F, bounds));
image.DebugSave(provider);
ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds);

57
tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs

@ -4,10 +4,18 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using SixLabors.ImageSharp.ColorSpaces;
using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSapce;
namespace SixLabors.ImageSharp.Tests
{
internal struct ApproximateFloatComparer : IEqualityComparer<float>, IEqualityComparer<Vector4>
internal struct ApproximateFloatComparer :
IEqualityComparer<float>,
IEqualityComparer<Vector4>,
IEqualityComparer<CieXyChromaticityCoordinates>,
IEqualityComparer<RgbPrimariesChromaticityCoordinates>,
IEqualityComparer<CieXyz>,
IEqualityComparer<IRgbWorkingSpace>
{
private readonly float Eps;
@ -37,5 +45,52 @@ namespace SixLabors.ImageSharp.Tests
{
throw new InvalidOperationException();
}
public bool Equals(CieXyChromaticityCoordinates x, CieXyChromaticityCoordinates y)
{
return this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y);
}
public int GetHashCode(CieXyChromaticityCoordinates obj)
{
throw new NotImplementedException();
}
public bool Equals(RgbPrimariesChromaticityCoordinates x, RgbPrimariesChromaticityCoordinates y)
{
return this.Equals(x.R, y.R) && this.Equals(x.G, y.G) && this.Equals(x.B, y.B);
}
public int GetHashCode(RgbPrimariesChromaticityCoordinates obj)
{
throw new NotImplementedException();
}
public bool Equals(CieXyz x, CieXyz y)
{
return this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y) && this.Equals(x.Z, y.Z);
}
public int GetHashCode(CieXyz obj)
{
throw new NotImplementedException();
}
public bool Equals(IRgbWorkingSpace x, IRgbWorkingSpace y)
{
if (x is IRgbWorkingSpace g1 && y is IRgbWorkingSpace g2)
{
return this.Equals(g1.WhitePoint, g2.WhitePoint)
&& this.Equals(g1.ChromaticityCoordinates, g2.ChromaticityCoordinates);
}
return this.Equals(x.WhitePoint, y.WhitePoint)
&& this.Equals(x.ChromaticityCoordinates, y.ChromaticityCoordinates);
}
public int GetHashCode(IRgbWorkingSpace obj)
{
throw new NotImplementedException();
}
}
}
Loading…
Cancel
Save