mirror of https://github.com/SixLabors/ImageSharp
95 changed files with 2786 additions and 1170 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.
|
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
using SixLabors.ImageSharp.Dithering.Base; |
|
||||
using SixLabors.ImageSharp.Memory; |
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
namespace SixLabors.ImageSharp.Dithering |
namespace SixLabors.ImageSharp.Dithering |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Applies error diffusion based dithering using the 4x4 ordered dithering matrix.
|
/// An ordered dithering matrix with equal sides of arbitrary length
|
||||
/// <see href="https://en.wikipedia.org/wiki/Ordered_dithering"/>
|
|
||||
/// </summary>
|
/// </summary>
|
||||
public sealed class OrderedDither : OrderedDitherBase |
public class OrderedDither : IOrderedDither |
||||
{ |
{ |
||||
/// <summary>
|
private readonly Fast2DArray<uint> thresholdMatrix; |
||||
/// The threshold matrix.
|
private readonly int modulusX; |
||||
/// This is calculated by multiplying each value in the original matrix by 16
|
private readonly int modulusY; |
||||
/// </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 } |
|
||||
}; |
|
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OrderedDither"/> class.
|
/// Initializes a new instance of the <see cref="OrderedDither"/> class.
|
||||
/// </summary>
|
/// </summary>
|
||||
public OrderedDither() |
/// <param name="length">The length of the matrix sides</param>
|
||||
: base(ThresholdMatrix) |
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 |
||||
@ -1,12 +1,14 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
namespace SixLabors.ImageSharp |
namespace SixLabors.ImageSharp |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Encapsulates the properties and methods that describe an image.
|
/// Encapsulates the properties and methods that describe an image.
|
||||
/// </summary>
|
/// </summary>
|
||||
public interface IImage : IImageInfo |
public interface IImage : IImageInfo, IDisposable |
||||
{ |
{ |
||||
} |
} |
||||
} |
} |
||||
@ -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 |
#!/bin/bash |
||||
|
|
||||
# Build in release mode |
# Build in release mode |
||||
dotnet build -c Release -f netcoreapp1.1 |
dotnet build -c Release -f netcoreapp2.0 |
||||
|
|
||||
# Run benchmarks |
# Run benchmarks |
||||
dotnet bin/Release/netcoreapp1.1/ImageSharp.Benchmarks.dll |
dotnet bin/Release/netcoreapp2.0/ImageSharp.Benchmarks.dll |
||||
@ -0,0 +1,35 @@ |
|||||
|
namespace SixLabors.ImageSharp.Tests |
||||
|
{ |
||||
|
using SixLabors.ImageSharp.Formats.Jpeg; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.ImageSharp.Processing; |
||||
|
using SixLabors.Primitives; |
||||
|
|
||||
|
using Xunit; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Might be useful to catch complex bugs
|
||||
|
/// </summary>
|
||||
|
public class ComplexIntegrationTests |
||||
|
{ |
||||
|
[Theory] |
||||
|
[WithFile(TestImages.Jpeg.Baseline.Snake, PixelTypes.Rgba32, 75, JpegSubsample.Ratio420)] |
||||
|
[WithFile(TestImages.Jpeg.Baseline.Lake, PixelTypes.Rgba32, 75, JpegSubsample.Ratio420)] |
||||
|
[WithFile(TestImages.Jpeg.Baseline.Snake, PixelTypes.Rgba32, 75, JpegSubsample.Ratio444)] |
||||
|
[WithFile(TestImages.Jpeg.Baseline.Lake, PixelTypes.Rgba32, 75, JpegSubsample.Ratio444)] |
||||
|
public void LoadResizeSave<TPixel>(TestImageProvider<TPixel> provider, int quality, JpegSubsample subsample) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
using (Image<TPixel> image = provider.GetImage(x => x.Resize(new ResizeOptions { Size = new Size(150, 100), Mode = ResizeMode.Max }))) |
||||
|
{ |
||||
|
|
||||
|
image.MetaData.ExifProfile = null; // Reduce the size of the file
|
||||
|
JpegEncoder options = new JpegEncoder { Subsample = subsample, Quality = quality }; |
||||
|
|
||||
|
provider.Utility.TestName += $"{subsample}_Q{quality}"; |
||||
|
provider.Utility.SaveTestOutputFile(image, "png"); |
||||
|
provider.Utility.SaveTestOutputFile(image, "jpg", options); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1 +1 @@ |
|||||
Subproject commit 376605e05bb704d425b2d17bf5b310f5376da22e |
Subproject commit b3be1178d4e970efc624181480094e50b0d57a90 |
||||
Loading…
Reference in new issue