mirror of https://github.com/SixLabors/ImageSharp
5 changed files with 244 additions and 34 deletions
@ -0,0 +1,50 @@ |
|||||
|
// <copyright file="Dither.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageSharp |
||||
|
{ |
||||
|
using System; |
||||
|
|
||||
|
using ImageSharp.Dithering; |
||||
|
using ImageSharp.Processing.Processors; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Extension methods for the <see cref="Image"/> type.
|
||||
|
/// </summary>
|
||||
|
public static partial class ImageExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Alters the alpha component of the image.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TColor">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{TColor}"/>.</returns>
|
||||
|
public static Image<TColor> Dither<TColor>(this Image<TColor> source, IErrorDiffuser diffuser, float threshold) |
||||
|
where TColor : struct, IPackedPixel, IEquatable<TColor> |
||||
|
{ |
||||
|
return Dither(source, diffuser, threshold, source.Bounds); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Alters the alpha component of the image.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TColor">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"/>.</returns>
|
||||
|
public static Image<TColor> Dither<TColor>(this Image<TColor> source, IErrorDiffuser diffuser, float threshold, Rectangle rectangle) |
||||
|
where TColor : struct, IPackedPixel, IEquatable<TColor> |
||||
|
{ |
||||
|
source.ApplyProcessor(new ErrorDiffusionDitherProcessor<TColor>(diffuser, threshold), rectangle); |
||||
|
return source; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,111 @@ |
|||||
|
// <copyright file="ErrorDiffusionDitherProcessor.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageSharp.Processing.Processors |
||||
|
{ |
||||
|
using System; |
||||
|
|
||||
|
using ImageSharp.Dithering; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// An <see cref="IImageProcessor{TColor}"/> that dithers an image using error diffusion.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TColor">The pixel format.</typeparam>
|
||||
|
public class ErrorDiffusionDitherProcessor<TColor> : ImageProcessor<TColor> |
||||
|
where TColor : struct, IPackedPixel, IEquatable<TColor> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="ErrorDiffusionDitherProcessor{TColor}"/> 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)); |
||||
|
|
||||
|
// TODO: Check thresholding limit. Colors should probably have Max/Min/Middle properties.
|
||||
|
Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); |
||||
|
|
||||
|
this.Diffuser = diffuser; |
||||
|
this.Threshold = threshold; |
||||
|
|
||||
|
// Default to white/black for upper/lower.
|
||||
|
TColor upper = default(TColor); |
||||
|
upper.PackFromBytes(255, 255, 255, 255); |
||||
|
this.UpperColor = upper; |
||||
|
|
||||
|
TColor lower = default(TColor); |
||||
|
lower.PackFromBytes(0, 0, 0, 255); |
||||
|
this.LowerColor = lower; |
||||
|
} |
||||
|
|
||||
|
/// <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 TColor UpperColor { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the color to use for pixels that fall below the threshold.
|
||||
|
/// </summary>
|
||||
|
public TColor LowerColor { get; set; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void BeforeApply(ImageBase<TColor> source, Rectangle sourceRectangle) |
||||
|
{ |
||||
|
new GrayscaleBt709Processor<TColor>().Apply(source, sourceRectangle); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void OnApply(ImageBase<TColor> source, Rectangle sourceRectangle) |
||||
|
{ |
||||
|
int startY = sourceRectangle.Y; |
||||
|
int endY = sourceRectangle.Bottom; |
||||
|
int startX = sourceRectangle.X; |
||||
|
int endX = sourceRectangle.Right; |
||||
|
|
||||
|
// Align start/end positions.
|
||||
|
int minX = Math.Max(0, startX); |
||||
|
int maxX = Math.Min(source.Width, endX); |
||||
|
int minY = Math.Max(0, startY); |
||||
|
int maxY = Math.Min(source.Height, endY); |
||||
|
|
||||
|
// Reset offset if necessary.
|
||||
|
if (minX > 0) |
||||
|
{ |
||||
|
startX = 0; |
||||
|
} |
||||
|
|
||||
|
if (minY > 0) |
||||
|
{ |
||||
|
startY = 0; |
||||
|
} |
||||
|
|
||||
|
using (PixelAccessor<TColor> sourcePixels = source.Lock()) |
||||
|
{ |
||||
|
for (int y = minY; y < maxY; y++) |
||||
|
{ |
||||
|
int offsetY = y - startY; |
||||
|
for (int x = minX; x < maxX; x++) |
||||
|
{ |
||||
|
int offsetX = x - startX; |
||||
|
TColor sourceColor = sourcePixels[offsetX, offsetY]; |
||||
|
TColor transformedColor = sourceColor.ToVector4().X >= this.Threshold ? this.UpperColor : this.LowerColor; |
||||
|
this.Diffuser.Dither(sourcePixels, sourceColor, transformedColor, offsetX, offsetY, maxX, maxY); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
// <copyright file="DitherTest.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageSharp.Tests |
||||
|
{ |
||||
|
using System.IO; |
||||
|
|
||||
|
using ImageSharp.Dithering; |
||||
|
|
||||
|
using Xunit; |
||||
|
|
||||
|
public class DitherTest : FileTestBase |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void ImageShouldApplyDitherFilter() |
||||
|
{ |
||||
|
string path = this.CreateOutputDirectory("Dither"); |
||||
|
|
||||
|
foreach (TestFile file in Files) |
||||
|
{ |
||||
|
using (Image image = file.CreateImage()) |
||||
|
using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) |
||||
|
{ |
||||
|
image.Dither(new SierraLite(), .5F).Save(output); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void ImageShouldApplyDitherFilterInBox() |
||||
|
{ |
||||
|
string path = this.CreateOutputDirectory("Dither"); |
||||
|
|
||||
|
foreach (TestFile file in Files) |
||||
|
{ |
||||
|
string filename = file.GetFileName("-InBox"); |
||||
|
using (Image image = file.CreateImage()) |
||||
|
using (FileStream output = File.OpenWrite($"{path}/{filename}")) |
||||
|
{ |
||||
|
image.Dither(new SierraLite(), .5F, new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue