Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature proposal: Dithering #457

Merged
merged 110 commits into from
Jan 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
110 commits
Select commit Hold shift + click to select a range
bf62d01
Feature proposal: Dithering
Aug 23, 2023
68b14c3
Fixed spelling mistakes to make the spell checker happy
Aug 23, 2023
39d1a29
Fixed more spelling mistakes to make the spell checker happy
Aug 23, 2023
00bfc1d
changed name to variable and used clamping functions in `Utility`
Aug 23, 2023
c66b6c9
Put new effect in `Pinta.Effects` namespace
Aug 23, 2023
9a181d7
Removed unnecessary comment
Aug 23, 2023
499a054
Added a new default palette
Aug 23, 2023
e35ceea
Corrected overflow that happened when creating web-safe color cube
Aug 23, 2023
186b28c
Overridden `LaunchConfiguration`, as a reminder in the code that it s…
Aug 24, 2023
33474f6
`ForwardErrorDiffusionDitheringData` now has enum-typed fields, so th…
Aug 24, 2023
39a3c1c
Added old ms paint palette
Aug 24, 2023
6dcb04b
Launches simple effect dialog
Aug 25, 2023
7739ae2
Debugged in Ubuntu and fixed indexing issues that prevented any pixel…
Aug 25, 2023
cc78400
Fixed formatting
Aug 25, 2023
39c44dd
Removed unnecessary class qualification
Aug 25, 2023
01b27a7
Started re-writing the algorithm
Aug 27, 2023
8636a6d
Fixed formatting
Aug 27, 2023
c2b5899
Another attempt, separating it into a 'working surface' and the desti…
Aug 27, 2023
400d18c
Fixed formatting
Aug 27, 2023
e351f9e
I finally understood how the ROIs are being passed as an argument. Th…
Aug 27, 2023
d4a8f50
Finally dithering works
Aug 27, 2023
19db393
Fixed formatting
Aug 27, 2023
6f54ffa
Formatting
Oct 2, 2023
87df8cb
Removed `DiffusionMatrixElement` and inheritors
Oct 2, 2023
1a15f18
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Oct 12, 2023
f1e02ae
Added missing property
Oct 12, 2023
6a25aae
Added properties for dithering
Oct 18, 2023
832410b
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Oct 20, 2023
2df07ec
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Oct 24, 2023
9e0da4a
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Dec 9, 2023
19ea99b
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Dec 10, 2023
d43748b
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Dec 13, 2023
7188947
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Dec 14, 2023
7c8b13c
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Dec 15, 2023
ab162d2
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Dec 16, 2023
8716b29
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Dec 17, 2023
b0a24a3
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Dec 17, 2023
df31596
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Dec 17, 2023
3afb1ee
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Dec 18, 2023
f171294
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Dec 18, 2023
e3992ce
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Dec 18, 2023
c96ee62
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Dec 19, 2023
64c845f
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Dec 20, 2023
a9c8b66
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Dec 20, 2023
836ca27
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Dec 21, 2023
91b9e19
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Dec 22, 2023
137c3fa
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Dec 23, 2023
2ec8999
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Dec 24, 2023
cbe0974
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Dec 25, 2023
dca995d
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Dec 27, 2023
5ab1109
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Dec 28, 2023
089ecd8
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Dec 28, 2023
91fbff0
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Dec 29, 2023
379106b
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Dec 29, 2023
74d4067
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Dec 30, 2023
bc58a71
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Dec 31, 2023
f35e299
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Jan 1, 2024
71fddbb
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Jan 4, 2024
dee5e3b
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Jan 4, 2024
874a482
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Jan 6, 2024
09f4850
Refactoring
Lehonti Jan 6, 2024
0235ed4
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Jan 6, 2024
f2450a7
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Jan 7, 2024
aa85be5
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Jan 7, 2024
d8fa12e
Changed method that was overridden
Lehonti Jan 9, 2024
213f0bf
Inlined method
Lehonti Jan 9, 2024
d45393d
formatting
Lehonti Jan 9, 2024
2b8b831
Assigning known settings to variables at the beginning of `Render` me…
Lehonti Jan 9, 2024
cbb23c0
`FindClosestPaletteColor` method made `static`
Lehonti Jan 9, 2024
d6dee49
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Jan 9, 2024
08611b3
Extracted settings
Lehonti Jan 9, 2024
4fe3095
Formatting
Lehonti Jan 9, 2024
f041ea6
Removed unused property
Lehonti Jan 9, 2024
51393fe
Moved comment to a place that makes it more readable
Lehonti Jan 9, 2024
4c6f8c5
Formatting and making some calculations more efficient (by removing u…
Lehonti Jan 9, 2024
494e4f6
Corrected naming
Lehonti Jan 9, 2024
dc24b89
Removed conditional compilation
Lehonti Jan 9, 2024
00e6f54
Added tests
Lehonti Jan 9, 2024
4fdd7d5
Do the tests succeed if I remove this?
Lehonti Jan 9, 2024
64208a8
Revert "Do the tests succeed if I remove this?"
Lehonti Jan 9, 2024
c539dd4
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Jan 10, 2024
13ad130
Renamed dithering effect and extracted some classes into their own files
Lehonti Jan 10, 2024
947858e
Copying to dst_data first, just to make sure
Lehonti Jan 10, 2024
2bfc006
fixed compilation errors
Lehonti Jan 10, 2024
702592d
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Jan 11, 2024
1fb54c8
Changed test picture `dithering2.png`
Lehonti Jan 11, 2024
1c4d1db
Ordered `using`s
Lehonti Jan 11, 2024
fdc8c52
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Jan 13, 2024
f1c64a0
Added checks so that when the diffusion matrix is being applied is st…
Lehonti Jan 13, 2024
b2fa05f
Changed test files
Lehonti Jan 13, 2024
5a73b5b
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Jan 13, 2024
7f88f7c
Expanded selection of color palettes
Lehonti Jan 13, 2024
9819be8
Sorted lines in `PaletteHelper`
Lehonti Jan 13, 2024
3a3cfb2
Expanded palette selection further
Lehonti Jan 13, 2024
6a725f9
Added another palette
Lehonti Jan 13, 2024
6468687
Added another palette
Lehonti Jan 13, 2024
4606508
Local refactoring
Lehonti Jan 13, 2024
adfd3e1
Removed WebSafe, as it was repeated
Lehonti Jan 13, 2024
2dc1247
Created `ColorCube` method to bring many of those palettes together
Lehonti Jan 13, 2024
f66b20d
Formatting
Lehonti Jan 13, 2024
a6d126e
Made name more specific
Lehonti Jan 13, 2024
7875e19
Added `Rgb12Bit` palette
Lehonti Jan 14, 2024
37d6749
"Compressed" the logic even further
Lehonti Jan 14, 2024
d618420
Made code even more compact
Lehonti Jan 14, 2024
5858023
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Jan 14, 2024
86a6123
Implemented suggested changes
Lehonti Jan 14, 2024
b15875a
Changed label
Lehonti Jan 14, 2024
733be7c
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Jan 14, 2024
1f80229
Merge branch 'PintaProject:master' into feature/error_diffusion_dithe…
Lehonti Jan 14, 2024
1372f7b
Added comments for translators
Lehonti Jan 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 173 additions & 0 deletions Pinta.Effects/Classes/ErrorDiffusionMatrix.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Pinta.Gui.Widgets;

namespace Pinta.Effects;

public enum PredefinedDiffusionMatrices
{
// Translators: Image dithering matrix named after Frankie Sierra
[Caption ("Sierra")]
Sierra,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should probably get Caption attributes added, e.g. Floyd-Steinberg should have a hyphen in it from what I see online, along with explanations for translators

It would also be good to see if there are any more descriptive names for users, e.g. "Fake" Floyd-Steinberg might not help a user understand what it does

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I added a CaptionAttribute to those values that have hyphens or spaces; and I labeled the matrix you mentioned as "Floyd-Steinberg Lite"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks - since these captions will also show up as translatable strings, also adding some Translators: ... comments would be good to explain to translators that these are algorithms named after people

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I just added comments for translators making it clear that the matrices were named after people


// Translators: Image dithering matrix named after Frankie Sierra
[Caption ("Two-Row Sierra")]
TwoRowSierra,

// Translators: Image dithering matrix named after Frankie Sierra
[Caption ("Sierra Lite")]
SierraLite,

// Translators: Image dithering matrix named after Daniel Burkes
[Caption ("Burkes")]
Burkes,

// Translators: Image dithering matrix named after Bill Atkinson
[Caption ("Atkinson")]
Atkinson,

// Translators: Image dithering matrix named after Peter Stucki
[Caption ("Stucki")]
Stucki,

// Translators: Image dithering matrix named after J. F. Jarvis, C. N. Judice, and W. H. Ninke
[Caption ("Jarvis-Judice-Ninke")]
JarvisJudiceNinke,

// Translators: Image dithering matrix named after Robert W. Floyd and Louis Steinberg
[Caption ("Floyd-Steinberg")]
FloydSteinberg,

// Translators: Image dithering matrix named after Robert W. Floyd and Louis Steinberg. Some software may use it and call it Floyd-Steinberg, but it's not the actual Floyd-Steinberg matrix
[Caption ("Floyd-Steinberg Lite")]
FalseFloydSteinberg,
}

/// <summary>
/// Represents the matrix that is used by the dithering algorithm
/// in order to propagate the error (defined as the difference
/// between a pixel's color and the color in a certain palette that is
/// closest to it) forward.
/// </summary>
internal sealed class ErrorDiffusionMatrix
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also add some doc comments for this class

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just added a doc comment with basic explanation

{
public static ErrorDiffusionMatrix GetPredefined (PredefinedDiffusionMatrices choice)
{
return choice switch {
PredefinedDiffusionMatrices.Sierra => Predefined.Sierra,
PredefinedDiffusionMatrices.TwoRowSierra => Predefined.TwoRowSierra,
PredefinedDiffusionMatrices.SierraLite => Predefined.SierraLite,
PredefinedDiffusionMatrices.Burkes => Predefined.Burkes,
PredefinedDiffusionMatrices.Atkinson => Predefined.Atkinson,
PredefinedDiffusionMatrices.Stucki => Predefined.Stucki,
PredefinedDiffusionMatrices.JarvisJudiceNinke => Predefined.JarvisJudiceNinke,
PredefinedDiffusionMatrices.FloydSteinberg => Predefined.FloydSteinberg,
PredefinedDiffusionMatrices.FalseFloydSteinberg => Predefined.FakeFloydSteinberg,
_ => throw new InvalidEnumArgumentException (nameof (choice), (int) choice, typeof (PredefinedDiffusionMatrices)),
};
}

public static class Predefined
{
public static ErrorDiffusionMatrix Sierra { get; } = new ErrorDiffusionMatrix (DefaultMatrixArrays.Sierra, 2);
public static ErrorDiffusionMatrix TwoRowSierra { get; } = new ErrorDiffusionMatrix (DefaultMatrixArrays.TwoRowSierra, 2);
public static ErrorDiffusionMatrix SierraLite { get; } = new ErrorDiffusionMatrix (DefaultMatrixArrays.SierraLite, 1);
public static ErrorDiffusionMatrix Burkes { get; } = new ErrorDiffusionMatrix (DefaultMatrixArrays.Burkes, 2);
public static ErrorDiffusionMatrix Atkinson { get; } = new ErrorDiffusionMatrix (DefaultMatrixArrays.Atkinson, 1);
public static ErrorDiffusionMatrix Stucki { get; } = new ErrorDiffusionMatrix (DefaultMatrixArrays.Stucki, 2);
public static ErrorDiffusionMatrix JarvisJudiceNinke { get; } = new ErrorDiffusionMatrix (DefaultMatrixArrays.JarvisJudiceNinke, 2);
public static ErrorDiffusionMatrix FloydSteinberg { get; } = new ErrorDiffusionMatrix (DefaultMatrixArrays.FloydSteinberg, 1);
public static ErrorDiffusionMatrix FakeFloydSteinberg { get; } = new ErrorDiffusionMatrix (DefaultMatrixArrays.FakeFloydSteinberg, 0);
}

private static class DefaultMatrixArrays
{
public static int[,] Sierra { get; } = {
{ 0, 0, 0, 5, 3, },
{ 2, 4, 5, 4, 2, },
{ 0, 2, 3, 2, 0, },
};

public static int[,] TwoRowSierra { get; } = {
{ 0, 0, 0, 4, 3, },
{ 1, 2, 3, 2, 1, },
};

public static int[,] SierraLite { get; } = {
{ 0, 0, 2, },
{ 1, 1, 0, },
};

public static int[,] Burkes { get; } = {
{ 0, 0, 0, 8, 4, },
{ 2, 4, 8, 4, 2, },
};

public static int[,] Atkinson { get; } = {
{ 0, 0, 1, 1, },
{ 1, 1, 1, 0, },
{ 0, 1, 0, 0, },
};

public static int[,] Stucki { get; } = {
{ 0, 0, 0, 8, 4, },
{ 2, 4, 8, 4, 2, },
{ 1, 2, 4, 2, 1, },
};

public static int[,] JarvisJudiceNinke { get; } = {
{ 0, 0, 0, 7, 5, },
{ 3, 5, 7, 5, 3, },
{ 1, 3, 5, 3, 1, },
};

public static int[,] FloydSteinberg { get; } = {
{ 0, 0, 7, },
{ 3, 5, 1, }
};

public static int[,] FakeFloydSteinberg { get; } = {
{ 0, 3, },
{ 3, 2, }
};
}

private readonly int[,] array_2_d;
public int Columns { get; }
public int Rows { get; }
public int TotalWeight { get; }
public int ColumnsToLeft { get; }
public int ColumnsToRight { get; }
public int RowsBelow { get; }
public int this[int row, int column] => array_2_d[row, column];
public ErrorDiffusionMatrix (int[,] array2D, int pixelColumn)
{
var clone = (int[,]) array2D.Clone ();
var rows = clone.GetLength (0);
if (rows <= 0) throw new ArgumentException ("Array has to have a strictly positive number of rows", nameof (array2D));
var columns = clone.GetLength (1);
if (columns <= 0) throw new ArgumentException ("Array has to have a strictly positive number of rows", nameof (array2D));
if (pixelColumn < 0) throw new ArgumentException ("Argument has to refer to a valid column offset", nameof (pixelColumn));
if (pixelColumn >= columns) throw new ArgumentException ("Argument has to refer to a valid column offset", nameof (pixelColumn));
if (clone[0, pixelColumn] != 0) throw new ArgumentException ("Target pixel cannot have a nonzero weight");
var flattened = Flatten2DArray (clone);
if (flattened.Any (w => w < 0)) throw new ArgumentException ("No negative weights", nameof (array2D));
if (flattened.Take (pixelColumn).Any (w => w != 0)) throw new ArgumentException ("Pixels previous to target cannot have nonzero weights");
ColumnsToLeft = pixelColumn;
ColumnsToRight = columns - 1 - pixelColumn;
TotalWeight = flattened.Sum ();
Columns = columns;
Rows = rows;
RowsBelow = rows - 1;
array_2_d = clone;
}

private static IEnumerable<T> Flatten2DArray<T> (T[,] array)
{
for (int i = 0; i < array.GetLength (0); i++)
for (int j = 0; j < array.GetLength (1); j++)
yield return array[i, j];
}
}
2 changes: 2 additions & 0 deletions Pinta.Effects/CoreEffectsExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public void Initialize ()
PintaCore.Effects.RegisterEffect (new CloudsEffect (services));
PintaCore.Effects.RegisterEffect (new EdgeDetectEffect ());
PintaCore.Effects.RegisterEffect (new EmbossEffect ());
PintaCore.Effects.RegisterEffect (new DitheringEffect ());
PintaCore.Effects.RegisterEffect (new FragmentEffect ());
PintaCore.Effects.RegisterEffect (new FrostedGlassEffect ());
PintaCore.Effects.RegisterEffect (new GaussianBlurEffect ());
Expand Down Expand Up @@ -105,6 +106,7 @@ public void Uninitialize ()
PintaCore.Effects.UnregisterInstanceOfEffect (typeof (CloudsEffect));
PintaCore.Effects.UnregisterInstanceOfEffect (typeof (EdgeDetectEffect));
PintaCore.Effects.UnregisterInstanceOfEffect (typeof (EmbossEffect));
PintaCore.Effects.UnregisterInstanceOfEffect (typeof (DitheringEffect));
PintaCore.Effects.UnregisterInstanceOfEffect (typeof (FragmentEffect));
PintaCore.Effects.UnregisterInstanceOfEffect (typeof (FrostedGlassEffect));
PintaCore.Effects.UnregisterInstanceOfEffect (typeof (GaussianBlurEffect));
Expand Down
143 changes: 143 additions & 0 deletions Pinta.Effects/Effects/DitheringEffect.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
using System;
using System.Collections.Immutable;
using Cairo;
using Pinta.Core;
using Pinta.Gui.Widgets;

namespace Pinta.Effects;

public sealed class DitheringEffect : BaseEffect
{
public override string Name => Translations.GetString ("Dithering");
public override bool IsConfigurable => true;
// TODO: Icon
public override string EffectMenuCategory => Translations.GetString ("Color");
public DitheringData Data => (DitheringData) EffectData!; // NRT - Set in constructor

public override bool IsTileable => false;

public DitheringEffect ()
{
EffectData = new DitheringData ();
}

public override void LaunchConfiguration ()
{
EffectHelper.LaunchSimpleEffectDialog (this);
}

private sealed record DitheringSettings (
ErrorDiffusionMatrix diffusionMatrix,
ImmutableArray<ColorBgra> palette,
int sourceWidth,
int sourceHeight);

private DitheringSettings CreateSettings (ImageSurface src)
=> new (
diffusionMatrix: ErrorDiffusionMatrix.GetPredefined (Data.ErrorDiffusionMethod),
palette: PaletteHelper.GetPredefined (Data.PaletteChoice),
sourceWidth: src.Width,
sourceHeight: src.Height
);

protected override void Render (ImageSurface src, ImageSurface dest, RectangleI roi)
{
DitheringSettings settings = CreateSettings (src);

ReadOnlySpan<ColorBgra> src_data = src.GetReadOnlyPixelData ();
Span<ColorBgra> dst_data = dest.GetPixelData ();

for (int y = roi.Top; y <= roi.Bottom; y++) {
for (int x = roi.Left; x <= roi.Right; x++) {
int currentIndex = y * settings.sourceWidth + x;
dst_data[currentIndex] = src_data[currentIndex];
}
}

for (int y = roi.Top; y <= roi.Bottom; y++) {

for (int x = roi.Left; x <= roi.Right; x++) {

int currentIndex = y * settings.sourceWidth + x;
ColorBgra originalPixel = dst_data[currentIndex];
ColorBgra closestColor = FindClosestPaletteColor (settings.palette, originalPixel);

dst_data[currentIndex] = closestColor;

int errorRed = originalPixel.R - closestColor.R;
int errorGreen = originalPixel.G - closestColor.G;
int errorBlue = originalPixel.B - closestColor.B;

for (int r = 0; r < settings.diffusionMatrix.Rows; r++) {

for (int c = 0; c < settings.diffusionMatrix.Columns; c++) {

var weight = settings.diffusionMatrix[r, c];

if (weight <= 0)
continue;

PointI thisItem = new (
X: x + c - settings.diffusionMatrix.ColumnsToLeft,
Y: y + r
);

if (thisItem.X < roi.Left || thisItem.X >= roi.Right)
continue;

if (thisItem.Y < roi.Top || thisItem.Y >= roi.Bottom)
continue;

int idx = (thisItem.Y * settings.sourceWidth) + thisItem.X;

double factor = ((double) weight) / settings.diffusionMatrix.TotalWeight;

dst_data[idx] = AddError (dst_data[idx], factor, errorRed, errorGreen, errorBlue);
}
}

}
}
}

private static ColorBgra AddError (ColorBgra color, double factor, int errorRed, int errorGreen, int errorBlue)
=> ColorBgra.FromBgra (
b: Utility.ClampToByte (color.B + (int) (factor * errorBlue)),
g: Utility.ClampToByte (color.G + (int) (factor * errorGreen)),
r: Utility.ClampToByte (color.R + (int) (factor * errorRed)),
a: 255
);

private static ColorBgra FindClosestPaletteColor (ImmutableArray<ColorBgra> palette, ColorBgra original)
{
if (palette.IsDefault) throw new ArgumentException ("Palette not initialized", nameof (palette));
if (palette.Length == 0) throw new ArgumentException ("Palette cannot be empty", nameof (palette));
if (palette.Length == 1) return palette[0];
double minDistance = double.MaxValue;
ColorBgra closestColor = ColorBgra.FromBgra (0, 0, 0, 1);
foreach (var paletteColor in palette) {
double distance = CalculateSquaredDistance (original, paletteColor);
if (distance >= minDistance) continue;
minDistance = distance;
closestColor = paletteColor;
}
return closestColor;
}

private static double CalculateSquaredDistance (ColorBgra color1, ColorBgra color2)
{
double deltaR = color1.R - color2.R;
double deltaG = color1.G - color2.G;
double deltaB = color1.B - color2.B;
return deltaR * deltaR + deltaG * deltaG + deltaB * deltaB;
}

public sealed class DitheringData : EffectData
{
[Caption ("Error Diffusion Method")]
public PredefinedDiffusionMatrices ErrorDiffusionMethod { get; set; } = PredefinedDiffusionMatrices.FloydSteinberg;

[Caption ("Palette")]
public PredefinedPalettes PaletteChoice { get; set; } = PredefinedPalettes.OldWindows16;
}
}
Loading