📷 A modern, cross-platform, 2D Graphics library for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

126 lines
4.5 KiB

// <copyright file="ErrorDiffuser.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Dithering
{
using System.Numerics;
using System.Runtime.CompilerServices;
using ImageSharp.PixelFormats;
/// <summary>
/// The base class for performing error diffusion based dithering.
/// </summary>
public abstract class ErrorDiffuser : IErrorDiffuser
{
/// <summary>
/// The vector to perform division.
/// </summary>
private readonly Vector4 divisorVector;
/// <summary>
/// The matrix width
/// </summary>
private readonly int matrixHeight;
/// <summary>
/// The matrix height
/// </summary>
private readonly int matrixWidth;
/// <summary>
/// The offset at which to start the dithering operation.
/// </summary>
private readonly int startingOffset;
/// <summary>
/// The diffusion matrix
/// </summary>
private readonly Fast2DArray<float> matrix;
/// <summary>
/// Initializes a new instance of the <see cref="ErrorDiffuser"/> class.
/// </summary>
/// <param name="matrix">The dithering matrix.</param>
/// <param name="divisor">The divisor.</param>
internal ErrorDiffuser(Fast2DArray<float> matrix, byte divisor)
{
Guard.NotNull(matrix, nameof(matrix));
Guard.MustBeGreaterThan(divisor, 0, nameof(divisor));
this.matrix = matrix;
this.matrixWidth = this.matrix.Width;
this.matrixHeight = this.matrix.Height;
this.divisorVector = new Vector4(divisor);
this.startingOffset = 0;
for (int i = 0; i < this.matrixWidth; i++)
{
// Good to disable here as we are not comparing matematical output.
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (matrix[0, i] != 0)
{
this.startingOffset = (byte)(i - 1);
break;
}
}
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dither<TPixel>(PixelAccessor<TPixel> pixels, TPixel source, TPixel transformed, int x, int y, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
this.Dither(pixels, source, transformed, x, y, width, height, true);
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dither<TPixel>(PixelAccessor<TPixel> pixels, TPixel source, TPixel transformed, int x, int y, int width, int height, bool replacePixel)
where TPixel : struct, IPixel<TPixel>
{
if (replacePixel)
{
// Assign the transformed pixel to the array.
pixels[x, y] = transformed;
}
// Calculate the error
Vector4 error = source.ToVector4() - transformed.ToVector4();
// Loop through and distribute the error amongst neighbouring pixels.
for (int row = 0; row < this.matrixHeight; row++)
{
int matrixY = y + row;
for (int col = 0; col < this.matrixWidth; col++)
{
int matrixX = x + (col - this.startingOffset);
if (matrixX > 0 && matrixX < width && matrixY > 0 && matrixY < height)
{
float coefficient = this.matrix[row, col];
// Good to disable here as we are not comparing mathematical output.
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (coefficient == 0)
{
continue;
}
Vector4 coefficientVector = new Vector4(coefficient);
Vector4 offsetColor = pixels[matrixX, matrixY].ToVector4();
Vector4 result = ((error * coefficientVector) / this.divisorVector) + offsetColor;
result.W = offsetColor.W;
TPixel packed = default(TPixel);
packed.PackFromVector4(result);
pixels[matrixX, matrixY] = packed;
}
}
}
}
}
}