mirror of https://github.com/SixLabors/ImageSharp
81 changed files with 2180 additions and 1023 deletions
@ -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> |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -1,3 +0,0 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:93bb4d6281dc1e845db57e836e0dca30b7a4062e81044efb27ad4d8b1a33130c |
|||
size 15787 |
|||
@ -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> |
|||
@ -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 |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
@ -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) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -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) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -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) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -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) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -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) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -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."); |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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 |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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()); |
|||
} |
|||
} |
|||
@ -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 |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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]); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue