├── resources
├── icon.ico
├── icon48.png
├── icon-16.png
├── icon-24.png
├── icon-32.png
└── DITHER.TXT
├── dithering-bayer8.png
├── dithering-atkinson.png
├── src
├── Resources
│ ├── Copy.png
│ ├── Open.png
│ ├── Paste.png
│ ├── Save.png
│ ├── Sample.png
│ ├── Copy and Append.png
│ ├── image-resize-actual.png
│ └── ui-splitter-horizontal.png
├── packages.config
├── App.config
├── Transforms
│ ├── IPixelTransform.cs
│ ├── SimpleIndexedPalettePixelTransform8.cs
│ ├── MonochromePixelTransform.cs
│ ├── SimpleIndexedPalettePixelTransform16.cs
│ ├── SimpleIndexedPalettePixelTransform.cs
│ └── SimpleIndexedPalettePixelTransform256.cs
├── Dithering
│ ├── Bayer2.cs
│ ├── Bayer3.cs
│ ├── Bayer4.cs
│ ├── IErrorDiffusion.cs
│ ├── Bayer8.cs
│ ├── FloydSteinbergDithering.cs
│ ├── SierraLiteDithering.cs
│ ├── BurkesDithering.cs
│ ├── Sierra2Dithering.cs
│ ├── AtkinsonDithering.cs
│ ├── Sierra3Dithering.cs
│ ├── StuckiDithering.cs
│ ├── JarvisJudiceNinkeDithering.cs
│ ├── RandomDithering.cs
│ ├── OrderedDithering.cs
│ └── ErrorDiffusionDithering.cs
├── WorkerData.cs
├── Helpers
│ ├── IntegerExtensions.cs
│ ├── ClipboardHelpers.cs
│ ├── ImageUtilities.cs
│ └── ArticleDiagrams.cs
├── Program.cs
├── Design
│ └── LineDesigner.cs
├── Properties
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ └── Resources.resx
├── ArgbColor.cs
├── AboutDialog.cs
├── Line.cs
├── AboutDialog.designer.cs
├── AboutDialog.resx
├── DitheringTest.csproj
├── MainForm.resx
├── GroupBox.cs
└── MainForm.cs
├── .gitignore
├── dithering-atkinson-color.png
├── nuget.config
├── .editorconfig
├── CHANGELOG.md
├── LICENSE.txt
├── DitheringTest.sln
└── README.md
/resources/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyotek/Dithering/HEAD/resources/icon.ico
--------------------------------------------------------------------------------
/dithering-bayer8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyotek/Dithering/HEAD/dithering-bayer8.png
--------------------------------------------------------------------------------
/resources/icon48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyotek/Dithering/HEAD/resources/icon48.png
--------------------------------------------------------------------------------
/dithering-atkinson.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyotek/Dithering/HEAD/dithering-atkinson.png
--------------------------------------------------------------------------------
/resources/icon-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyotek/Dithering/HEAD/resources/icon-16.png
--------------------------------------------------------------------------------
/resources/icon-24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyotek/Dithering/HEAD/resources/icon-24.png
--------------------------------------------------------------------------------
/resources/icon-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyotek/Dithering/HEAD/resources/icon-32.png
--------------------------------------------------------------------------------
/src/Resources/Copy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyotek/Dithering/HEAD/src/Resources/Copy.png
--------------------------------------------------------------------------------
/src/Resources/Open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyotek/Dithering/HEAD/src/Resources/Open.png
--------------------------------------------------------------------------------
/src/Resources/Paste.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyotek/Dithering/HEAD/src/Resources/Paste.png
--------------------------------------------------------------------------------
/src/Resources/Save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyotek/Dithering/HEAD/src/Resources/Save.png
--------------------------------------------------------------------------------
/src/Resources/Sample.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyotek/Dithering/HEAD/src/Resources/Sample.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.suo
2 | *.user
3 | *.ndproj
4 |
5 | packages
6 | bin
7 | obj
8 | NDependOut
9 | optimize.bat
--------------------------------------------------------------------------------
/dithering-atkinson-color.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyotek/Dithering/HEAD/dithering-atkinson-color.png
--------------------------------------------------------------------------------
/src/Resources/Copy and Append.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyotek/Dithering/HEAD/src/Resources/Copy and Append.png
--------------------------------------------------------------------------------
/src/Resources/image-resize-actual.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyotek/Dithering/HEAD/src/Resources/image-resize-actual.png
--------------------------------------------------------------------------------
/nuget.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/Resources/ui-splitter-horizontal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cyotek/Dithering/HEAD/src/Resources/ui-splitter-horizontal.png
--------------------------------------------------------------------------------
/src/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | ; http://editorconfig.org/
2 | ; https://visualstudiogallery.msdn.microsoft.com/c8bccfe2-650c-4b42-bc5c-845e21f96328
3 |
4 | root=true
5 |
6 | [*]
7 | end_of_line = crlf
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 | charset = utf-8
11 |
12 | [*.cs]
13 | indent_style = space
14 | indent_size = 2
--------------------------------------------------------------------------------
/src/Transforms/IPixelTransform.cs:
--------------------------------------------------------------------------------
1 | using Cyotek.Drawing;
2 |
3 | namespace Cyotek.DitheringTest.Transforms
4 | {
5 | internal interface IPixelTransform
6 | {
7 | #region Methods
8 |
9 | ArgbColor Transform(ArgbColor[] data, ArgbColor pixel, int x, int y, int width, int height);
10 |
11 | #endregion
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Change Log
2 | ==========
3 |
4 | 1.0.1.0
5 | -------
6 | * Namespaces adjusted to account for library linking
7 | * Added new base class to handle error diffusion algorithms by simply specifying a matrix
8 | * Added Jarvis, Judice & Ninke, Sierra3, Sierra2, SierraLite, Stucki and Atkinson algorithms
9 | * Added Random dithering
10 |
11 | 1.0.0.0
12 | -------
13 | * Initial release
14 |
--------------------------------------------------------------------------------
/src/Dithering/Bayer2.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 |
3 | // ReSharper disable once CheckNamespace
4 | namespace Cyotek.Drawing.Imaging.ColorReduction
5 | {
6 | [Description("Bayer-2")]
7 | [Browsable(false)]
8 | public class Bayer2 : OrderedDithering
9 | {
10 | #region Constructors
11 |
12 | public Bayer2()
13 | : base(new byte[,]
14 | {
15 | { 0, 2 },
16 | { 3, 1 }
17 | })
18 | { }
19 |
20 | #endregion
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Dithering/Bayer3.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 |
3 | // ReSharper disable once CheckNamespace
4 | namespace Cyotek.Drawing.Imaging.ColorReduction
5 | {
6 | [Description("Bayer-3")]
7 | [Browsable(false)]
8 | public class Bayer3 : OrderedDithering
9 | {
10 | #region Constructors
11 |
12 | public Bayer3()
13 | : base(new byte[,]
14 | {
15 | { 0, 7, 3 },
16 | { 6, 5, 2 },
17 | { 4, 1, 8 }
18 | })
19 | { }
20 |
21 | #endregion
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/WorkerData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Drawing;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using Cyotek.DitheringTest.Transforms;
8 | using Cyotek.Drawing.Imaging.ColorReduction;
9 |
10 | namespace Cyotek.DitheringTest
11 | {
12 | internal sealed class WorkerData
13 | {
14 | public Bitmap Image { get; set; }
15 |
16 | public IErrorDiffusion Dither { get; set; }
17 |
18 | public IPixelTransform Transform { get; set; }
19 |
20 | public int ColorCount { get; set; }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Dithering/Bayer4.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 |
3 | // ReSharper disable once CheckNamespace
4 | namespace Cyotek.Drawing.Imaging.ColorReduction
5 | {
6 | [Description("Bayer-4")]
7 | [Browsable(false)]
8 | public class Bayer4 : OrderedDithering
9 | {
10 | #region Constructors
11 |
12 | public Bayer4()
13 | : base(new byte[,]
14 | {
15 | { 0, 8, 2, 10 },
16 | { 12, 4, 14, 6 },
17 | { 3, 11, 1, 9 },
18 | { 15, 7, 13, 5 }
19 | })
20 | { }
21 |
22 | #endregion
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Dithering/IErrorDiffusion.cs:
--------------------------------------------------------------------------------
1 | /* Dithering an image using the Floyd–Steinberg algorithm in C#
2 | * https://www.cyotek.com/blog/dithering-an-image-using-the-floyd-steinberg-algorithm-in-csharp
3 | *
4 | * Copyright © 2015 Cyotek Ltd.
5 | *
6 | * Licensed under the MIT License. See LICENSE.txt for the full text.
7 | */
8 |
9 | namespace Cyotek.Drawing.Imaging.ColorReduction
10 | {
11 | public interface IErrorDiffusion
12 | {
13 | #region Methods
14 |
15 | void Diffuse(ArgbColor[] data, ArgbColor original, ArgbColor transformed, int x, int y, int width, int height);
16 |
17 | bool Prescan { get; }
18 |
19 | #endregion
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Helpers/IntegerExtensions.cs:
--------------------------------------------------------------------------------
1 | /* Dithering an image using the Floyd–Steinberg algorithm in C#
2 | * https://www.cyotek.com/blog/dithering-an-image-using-the-floyd-steinberg-algorithm-in-csharp
3 | *
4 | * Copyright © 2015 Cyotek Ltd.
5 | *
6 | * Licensed under the MIT License. See LICENSE.txt for the full text.
7 | */
8 |
9 | namespace Cyotek
10 | {
11 | internal static class IntegerExtensions
12 | {
13 | #region Static Methods
14 |
15 | internal static byte ToByte(this int value)
16 | {
17 | if (value < 0)
18 | {
19 | value = 0;
20 | }
21 | else if (value > 255)
22 | {
23 | value = 255;
24 | }
25 |
26 | return (byte)value;
27 | }
28 |
29 | #endregion
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows.Forms;
3 |
4 | /* Dithering an image using the Floyd–Steinberg algorithm in C#
5 | * https://www.cyotek.com/blog/dithering-an-image-using-the-floyd-steinberg-algorithm-in-csharp
6 | *
7 | * Copyright © 2015 Cyotek Ltd.
8 | *
9 | * Licensed under the MIT License. See LICENSE.txt for the full text.
10 | */
11 |
12 | namespace Cyotek.DitheringTest
13 | {
14 | internal static class Program
15 | {
16 | #region Static Methods
17 |
18 | ///
19 | /// The main entry point for the application.
20 | ///
21 | [STAThread]
22 | private static void Main()
23 | {
24 | Application.EnableVisualStyles();
25 | Application.SetCompatibleTextRenderingDefault(false);
26 | Application.Run(new MainForm());
27 | }
28 |
29 | #endregion
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Dithering/Bayer8.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 |
3 | // ReSharper disable once CheckNamespace
4 | namespace Cyotek.Drawing.Imaging.ColorReduction
5 | {
6 | [Description("Bayer-8")]
7 | [Browsable(false)]
8 | public class Bayer8 : OrderedDithering
9 | {
10 | #region Constructors
11 |
12 | public Bayer8()
13 | : base(new byte[,]
14 | {
15 | { 0, 48, 12, 60, 3, 51, 15, 63 },
16 | { 32, 16, 44, 28, 35, 19, 47, 31 },
17 | { 8, 56, 4, 52, 11, 59, 7, 55 },
18 | { 40, 24, 36, 20, 43, 27, 39, 23 },
19 | { 2, 50, 14, 62, 1, 49, 13, 61 },
20 | { 34, 18, 46, 30, 33, 17, 45, 29 },
21 | { 10, 58, 6, 54, 9, 57, 5, 53 },
22 | { 42, 26, 38, 22, 41, 25, 37, 21 }
23 | })
24 | { }
25 |
26 | #endregion
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Dithering/FloydSteinbergDithering.cs:
--------------------------------------------------------------------------------
1 | /* Dithering an image using the Floyd–Steinberg algorithm in C#
2 | * https://www.cyotek.com/blog/dithering-an-image-using-the-floyd-steinberg-algorithm-in-csharp
3 | *
4 | * Copyright © 2015 Cyotek Ltd.
5 | *
6 | * Licensed under the MIT License. See LICENSE.txt for the full text.
7 | */
8 |
9 | using System.ComponentModel;
10 |
11 | namespace Cyotek.Drawing.Imaging.ColorReduction
12 | {
13 | [Description("Floyd-Steinberg")]
14 | public sealed class FloydSteinbergDithering : ErrorDiffusionDithering
15 | {
16 | #region Constructors
17 |
18 | public FloydSteinbergDithering()
19 | : base(new byte[,]
20 | {
21 | {
22 | 0, 0, 7
23 | },
24 | {
25 | 3, 5, 1
26 | }
27 | }, 4, true)
28 | { }
29 |
30 | #endregion
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Design/LineDesigner.cs:
--------------------------------------------------------------------------------
1 | using System.Windows.Forms;
2 | using System.Windows.Forms.Design;
3 |
4 | namespace Cyotek.Windows.Forms.Design
5 | {
6 | public class LineDesigner : ControlDesigner
7 | {
8 | #region Overridden Properties
9 |
10 | public override SelectionRules SelectionRules
11 | {
12 | get
13 | {
14 | SelectionRules result;
15 |
16 | result = SelectionRules.Visible | SelectionRules.Moveable;
17 |
18 | switch (((Line)this.Control).Orientation)
19 | {
20 | case Orientation.Horizontal:
21 | result |= (SelectionRules.RightSizeable | SelectionRules.LeftSizeable);
22 | break;
23 | default:
24 | result |= (SelectionRules.TopSizeable | SelectionRules.BottomSizeable);
25 | break;
26 | }
27 |
28 | return result;
29 | }
30 | }
31 |
32 | #endregion
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Transforms/SimpleIndexedPalettePixelTransform8.cs:
--------------------------------------------------------------------------------
1 | using Cyotek.Drawing;
2 |
3 | /* Finding nearest colors using Euclidean distance
4 | * https://www.cyotek.com/blog/finding-nearest-colors-using-euclidean-distance
5 | *
6 | * Copyright © 2017 Cyotek Ltd.
7 | */
8 |
9 | namespace Cyotek.DitheringTest.Transforms
10 | {
11 | internal sealed class SimpleIndexedPalettePixelTransform8 : SimpleIndexedPalettePixelTransform
12 | {
13 | #region Constructors
14 |
15 | public SimpleIndexedPalettePixelTransform8()
16 | : base(new[]
17 | {
18 | ArgbColor.FromArgb(0, 0, 0),
19 | ArgbColor.FromArgb(255, 0, 0),
20 | ArgbColor.FromArgb(0, 255, 0),
21 | ArgbColor.FromArgb(0, 0, 255),
22 | ArgbColor.FromArgb(255, 255, 0),
23 | ArgbColor.FromArgb(255, 0, 255),
24 | ArgbColor.FromArgb(0, 255, 255),
25 | ArgbColor.FromArgb(255, 255, 255)
26 | })
27 | { }
28 |
29 | #endregion
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Dithering/SierraLiteDithering.cs:
--------------------------------------------------------------------------------
1 | /* Even more algorithms for dithering images using C#
2 | * https://www.cyotek.com/blog/even-more-algorithms-for-dithering-images-using-csharp
3 | *
4 | * Copyright © 2015 Cyotek Ltd.
5 | *
6 | * Licensed under the MIT License. See LICENSE.txt for the full text.
7 | */
8 |
9 | /*
10 | * Sierra Lite Dithering
11 | * http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT
12 | *
13 | * * 2/4
14 | * 1/4 1/4
15 | */
16 |
17 | using System.ComponentModel;
18 |
19 | namespace Cyotek.Drawing.Imaging.ColorReduction
20 | {
21 | [Description("Sierra Lite")]
22 | public sealed class SierraLiteDithering : ErrorDiffusionDithering
23 | {
24 | #region Constructors
25 |
26 | public SierraLiteDithering()
27 | : base(new byte[,]
28 | {
29 | {
30 | 0, 0, 2
31 | },
32 | {
33 | 1, 1, 0
34 | }
35 | }, 2, true)
36 | { }
37 |
38 | #endregion
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 | using System.Runtime.InteropServices;
4 |
5 | /* Dithering an image using the Floyd–Steinberg algorithm in C#
6 | * https://www.cyotek.com/blog/dithering-an-image-using-the-floyd-steinberg-algorithm-in-csharp
7 | *
8 | * Copyright © 2015 Cyotek Ltd.
9 | *
10 | * Licensed under the MIT License. See LICENSE.txt for the full text.
11 | */
12 |
13 | [assembly: AssemblyTitle("Cyotek Dithering Test")]
14 | [assembly: AssemblyDescription("")]
15 | [assembly: AssemblyConfiguration("")]
16 | [assembly: AssemblyCompany("Cyotek Ltd")]
17 | [assembly: AssemblyProduct("Cyotek Dithering Test")]
18 | [assembly: AssemblyCopyright("Copyright © 2015-2017 Cyotek Ltd. All Rights Reserved.")]
19 | [assembly: AssemblyTrademark("")]
20 | [assembly: AssemblyCulture("")]
21 | [assembly: ComVisible(false)]
22 | [assembly: CLSCompliant(true)]
23 | [assembly: Guid("b57f3270-6331-4939-bb84-959f2f2aae19")]
24 | [assembly: AssemblyVersion("1.0.0.0")]
25 | [assembly: AssemblyFileVersion("1.0.0.0")]
26 |
--------------------------------------------------------------------------------
/src/Dithering/BurkesDithering.cs:
--------------------------------------------------------------------------------
1 | /* Dithering an image using the Burkes algorithm in C#
2 | * https://www.cyotek.com/blog/dithering-an-image-using-the-burkes-algorithm-in-csharp
3 | *
4 | * Copyright © 2015 Cyotek Ltd.
5 | *
6 | * Licensed under the MIT License. See LICENSE.txt for the full text.
7 | */
8 |
9 | /*
10 | * Burkes Dithering
11 | * http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT
12 | *
13 | * * 8/32 4/32
14 | * 2/32 4/32 8/32 4/32 2/32
15 | */
16 |
17 | using System.ComponentModel;
18 |
19 | namespace Cyotek.Drawing.Imaging.ColorReduction
20 | {
21 | [Description("Burkes")]
22 | public sealed class BurksDithering : ErrorDiffusionDithering
23 | {
24 | #region Constructors
25 |
26 | public BurksDithering()
27 | : base(new byte[,]
28 | {
29 | {
30 | 0, 0, 0, 8, 4
31 | },
32 | {
33 | 2, 4, 8, 4, 2
34 | }
35 | }, 5, true)
36 | { }
37 |
38 | #endregion
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Dithering/Sierra2Dithering.cs:
--------------------------------------------------------------------------------
1 | /* Even more algorithms for dithering images using C#
2 | * https://www.cyotek.com/blog/even-more-algorithms-for-dithering-images-using-csharp
3 | *
4 | * Copyright © 2015 Cyotek Ltd.
5 | *
6 | * Licensed under the MIT License. See LICENSE.txt for the full text.
7 | */
8 |
9 | /*
10 | * Two Row Sierra Dithering
11 | * http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT
12 | *
13 | * * 4/16 3/16
14 | * 1/16 2/16 3/16 2/16 1/16
15 | */
16 |
17 | using System.ComponentModel;
18 |
19 | namespace Cyotek.Drawing.Imaging.ColorReduction
20 | {
21 | [Description("Two-Row Sierra")]
22 | public sealed class Sierra2Dithering : ErrorDiffusionDithering
23 | {
24 | #region Constructors
25 |
26 | public Sierra2Dithering()
27 | : base(new byte[,]
28 | {
29 | {
30 | 0, 0, 0, 4, 3
31 | },
32 | {
33 | 1, 2, 3, 2, 1
34 | }
35 | }, 4, true)
36 | { }
37 |
38 | #endregion
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright © 2015-2017 Cyotek Ltd.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/src/Dithering/AtkinsonDithering.cs:
--------------------------------------------------------------------------------
1 | /* Even more algorithms for dithering images using C#
2 | * https://www.cyotek.com/blog/even-more-algorithms-for-dithering-images-using-csharp
3 | *
4 | * Copyright © 2015 Cyotek Ltd.
5 | *
6 | * Licensed under the MIT License. See LICENSE.txt for the full text.
7 | */
8 |
9 | /*
10 | * Atkinson Dithering
11 | * http://www.tannerhelland.com/4660/dithering-eleven-algorithms-source-code/
12 | *
13 | * * 1/8 1/8
14 | * 1/8 1/8 1/8
15 | * 1/8
16 | */
17 |
18 | using System.ComponentModel;
19 |
20 | namespace Cyotek.Drawing.Imaging.ColorReduction
21 | {
22 | [Description("Atkinson")]
23 | public sealed class AtkinsonDithering : ErrorDiffusionDithering
24 | {
25 | #region Constructors
26 |
27 | public AtkinsonDithering()
28 | : base(new byte[,]
29 | {
30 | {
31 | 0, 0, 1, 1
32 | },
33 | {
34 | 1, 1, 1, 0
35 | },
36 | {
37 | 0, 1, 0, 0
38 | }
39 | }, 3, true)
40 | { }
41 |
42 | #endregion
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Dithering/Sierra3Dithering.cs:
--------------------------------------------------------------------------------
1 | /* Even more algorithms for dithering images using C#
2 | * https://www.cyotek.com/blog/even-more-algorithms-for-dithering-images-using-csharp
3 | *
4 | * Copyright © 2015 Cyotek Ltd.
5 | *
6 | * Licensed under the MIT License. See LICENSE.txt for the full text.
7 | */
8 |
9 | /*
10 | * Sierra Dithering
11 | * http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT
12 | *
13 | * * 5/32 3/32
14 | * 2/32 4/32 5/32 4/32 2/32
15 | * 2/32 3/32 2/32
16 | */
17 |
18 | using System.ComponentModel;
19 |
20 | namespace Cyotek.Drawing.Imaging.ColorReduction
21 | {
22 | [Description("Sierra")]
23 | public sealed class Sierra3Dithering : ErrorDiffusionDithering
24 | {
25 | #region Constructors
26 |
27 | public Sierra3Dithering()
28 | : base(new byte[,]
29 | {
30 | {
31 | 0, 0, 0, 5, 3
32 | },
33 | {
34 | 2, 4, 5, 4, 2
35 | },
36 | {
37 | 0, 2, 3, 2, 0
38 | }
39 | }, 5, true)
40 | { }
41 |
42 | #endregion
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Dithering/StuckiDithering.cs:
--------------------------------------------------------------------------------
1 | /* Even more algorithms for dithering images using C#
2 | * https://www.cyotek.com/blog/even-more-algorithms-for-dithering-images-using-csharp
3 | *
4 | * Copyright © 2015 Cyotek Ltd.
5 | *
6 | * Licensed under the MIT License. See LICENSE.txt for the full text.
7 | */
8 |
9 | /*
10 | * Stucki Dithering
11 | * http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT
12 | *
13 | * * 8/42 4/42
14 | * 2/42 4/42 8/42 4/42 2/42
15 | * 1/42 2/42 4/42 2/42 1/42
16 | */
17 |
18 | using System.ComponentModel;
19 |
20 | namespace Cyotek.Drawing.Imaging.ColorReduction
21 | {
22 | [Description("Stucki")]
23 | public sealed class StuckiDithering : ErrorDiffusionDithering
24 | {
25 | #region Constructors
26 |
27 | public StuckiDithering()
28 | : base(new byte[,]
29 | {
30 | {
31 | 0, 0, 0, 8, 4
32 | },
33 | {
34 | 2, 4, 8, 4, 2
35 | },
36 | {
37 | 1, 2, 4, 2, 1
38 | }
39 | }, 42, false)
40 | { }
41 |
42 | #endregion
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Dithering/JarvisJudiceNinkeDithering.cs:
--------------------------------------------------------------------------------
1 | /* Even more algorithms for dithering images using C#
2 | * https://www.cyotek.com/blog/even-more-algorithms-for-dithering-images-using-csharp
3 | *
4 | * Copyright © 2015 Cyotek Ltd.
5 | *
6 | * Licensed under the MIT License. See LICENSE.txt for the full text.
7 | */
8 |
9 | /*
10 | * Jarvis, Judice & Ninke Dithering
11 | * http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT
12 | *
13 | * * 8/48 5/48
14 | * 3/48 5/48 7/48 5/48 3/48
15 | * 1/48 3/48 5/48 3/48 1/48
16 | */
17 |
18 | using System.ComponentModel;
19 |
20 | namespace Cyotek.Drawing.Imaging.ColorReduction
21 | {
22 | [Description("Jarvis, Judice & Ninke")]
23 | public sealed class JarvisJudiceNinkeDithering : ErrorDiffusionDithering
24 | {
25 | #region Constructors
26 |
27 | public JarvisJudiceNinkeDithering()
28 | : base(new byte[,]
29 | {
30 | {
31 | 0, 0, 0, 7, 5
32 | },
33 | {
34 | 3, 5, 7, 5, 3
35 | },
36 | {
37 | 1, 3, 5, 3, 1
38 | }
39 | }, 48, false)
40 | { }
41 |
42 | #endregion
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Transforms/MonochromePixelTransform.cs:
--------------------------------------------------------------------------------
1 | using Cyotek.Drawing;
2 |
3 | namespace Cyotek.DitheringTest.Transforms
4 | {
5 | internal sealed class MonochromePixelTransform : IPixelTransform
6 | {
7 | #region Constants
8 |
9 | private readonly ArgbColor _black;
10 |
11 | private readonly byte _threshold;
12 |
13 | private readonly ArgbColor _white;
14 |
15 | #endregion
16 |
17 | #region Constructors
18 |
19 | public MonochromePixelTransform(byte threshold)
20 | {
21 | _threshold = threshold;
22 | _black = new ArgbColor(0, 0, 0);
23 | _white = new ArgbColor(255, 255, 255);
24 | }
25 |
26 | #endregion
27 |
28 | #region IPixelTransform Interface
29 |
30 | public ArgbColor Transform(ArgbColor[] data, ArgbColor pixel, int x, int y, int width, int height)
31 | {
32 | byte gray;
33 |
34 | gray = (byte)(0.299 * pixel.R + 0.587 * pixel.G + 0.114 * pixel.B);
35 |
36 | /*
37 | * I'm leaving the alpha channel untouched instead of making it fully opaque
38 | * otherwise the transparent areas become fully black, and I was getting annoyed
39 | * by this when testing images with large swathes of transparency!
40 | */
41 |
42 | return gray < _threshold ? _black : _white;
43 | }
44 |
45 | #endregion
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Transforms/SimpleIndexedPalettePixelTransform16.cs:
--------------------------------------------------------------------------------
1 | using Cyotek.Drawing;
2 |
3 | /* Finding nearest colors using Euclidean distance
4 | * https://www.cyotek.com/blog/finding-nearest-colors-using-euclidean-distance
5 | *
6 | * Copyright © 2017 Cyotek Ltd.
7 | */
8 |
9 | namespace Cyotek.DitheringTest.Transforms
10 | {
11 | internal sealed class SimpleIndexedPalettePixelTransform16 : SimpleIndexedPalettePixelTransform
12 | {
13 | #region Constructors
14 |
15 | public SimpleIndexedPalettePixelTransform16()
16 | : base(new[]
17 | {
18 | ArgbColor.FromArgb(0, 0, 0),
19 | ArgbColor.FromArgb(128, 0, 0),
20 | ArgbColor.FromArgb(0, 128, 0),
21 | ArgbColor.FromArgb(128, 128, 0),
22 | ArgbColor.FromArgb(0, 0, 128),
23 | ArgbColor.FromArgb(128, 0, 128),
24 | ArgbColor.FromArgb(0, 128, 128),
25 | ArgbColor.FromArgb(128, 128, 128),
26 | ArgbColor.FromArgb(192, 192, 192),
27 | ArgbColor.FromArgb(255, 0, 0),
28 | ArgbColor.FromArgb(0, 255, 0),
29 | ArgbColor.FromArgb(255, 255, 0),
30 | ArgbColor.FromArgb(0, 0, 255),
31 | ArgbColor.FromArgb(255, 0, 255),
32 | ArgbColor.FromArgb(0, 255, 255),
33 | ArgbColor.FromArgb(255, 255, 255)
34 | })
35 | { }
36 |
37 | #endregion
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/DitheringTest.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 14
4 | VisualStudioVersion = 14.0.25420.1
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DitheringTest", "src\DitheringTest.csproj", "{5192C96D-2219-41FB-A94D-617900FDDBDC}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Files", "Solution Files", "{7A90C58E-9210-4B9E-9A96-3A38CACAE804}"
9 | ProjectSection(SolutionItems) = preProject
10 | CHANGELOG.md = CHANGELOG.md
11 | resources\DHALF.TXT = resources\DHALF.TXT
12 | resources\DITHER.TXT = resources\DITHER.TXT
13 | LICENSE.txt = LICENSE.txt
14 | README.md = README.md
15 | EndProjectSection
16 | EndProject
17 | Global
18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
19 | Debug|Any CPU = Debug|Any CPU
20 | Release|Any CPU = Release|Any CPU
21 | EndGlobalSection
22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
23 | {5192C96D-2219-41FB-A94D-617900FDDBDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24 | {5192C96D-2219-41FB-A94D-617900FDDBDC}.Debug|Any CPU.Build.0 = Debug|Any CPU
25 | {5192C96D-2219-41FB-A94D-617900FDDBDC}.Release|Any CPU.ActiveCfg = Release|Any CPU
26 | {5192C96D-2219-41FB-A94D-617900FDDBDC}.Release|Any CPU.Build.0 = Release|Any CPU
27 | EndGlobalSection
28 | GlobalSection(SolutionProperties) = preSolution
29 | HideSolutionNode = FALSE
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Image Dithering using C#
2 | ========================
3 |
4 | This sample program demonstrates how to use various algorithms to dither an image using C#.
5 |
6 | 
7 |
8 | Please refer to the following articles on cyotek.com for more details
9 |
10 | * [An introduction to dithering images](https://www.cyotek.com/blog/an-introduction-to-dithering-images)
11 | * [Dithering an image using the Floyd–Steinberg algorithm in C#](https://www.cyotek.com/blog/dithering-an-image-using-the-floyd-steinberg-algorithm-in-csharp)
12 | * [Dithering an image using the Burkes algorithm in C#](https://www.cyotek.com/blog/dithering-an-image-using-the-burkes-algorithm-in-csharp)
13 | * [Even more algorithms for dithering images using C#](https://www.cyotek.com/blog/even-more-algorithms-for-dithering-images-using-csharp)
14 | * [Finding nearest colors using Euclidean distance](https://www.cyotek.com/blog/finding-nearest-colors-using-euclidean-distance)
15 |
16 | Resources
17 | ---------
18 | `DHALF.TXT` was obtained from
19 | `DITHER.TXT` was obtained from
20 |
21 | Referenced, but missing - if you find copies of these documents let [me know!](https://github.com/cyotek/Dithering/issues)
22 |
23 | * BURKES.ARC
24 | * NUDTHR.ARC
25 | * IDTVGA.TXT
26 | * DGIF.ZIP
27 |
28 | Screenshots
29 | -----------
30 |
31 | An example of Atkinson error diffusion, this time used in conjunction with a 256 fixed palette quantization.
32 |
33 | 
34 |
35 | An example of ordering dithering using an 8x8 Bayer matrix, demonstrating the classic hatching patterns associated with this style of dithering.
36 |
37 | 
38 |
--------------------------------------------------------------------------------
/src/Dithering/RandomDithering.cs:
--------------------------------------------------------------------------------
1 | /* Even more algorithms for dithering images using C#
2 | * https://www.cyotek.com/blog/even-more-algorithms-for-dithering-images-using-csharp
3 | *
4 | * Copyright © 2015 Cyotek Ltd.
5 | *
6 | * Licensed under the MIT License. See LICENSE.txt for the full text.
7 | */
8 |
9 | using System;
10 | using System.ComponentModel;
11 |
12 | namespace Cyotek.Drawing.Imaging.ColorReduction
13 | {
14 | [Description("Random")]
15 | [Browsable(false)]
16 | public sealed class RandomDithering : IErrorDiffusion
17 | {
18 | #region Constants
19 |
20 | private readonly ArgbColor _black;
21 |
22 | private static readonly ArgbColor _blackDefault = new ArgbColor(255, 0, 0, 0);
23 |
24 | private readonly Random _random;
25 |
26 | private readonly ArgbColor _white;
27 |
28 | private static readonly ArgbColor _whiteDefault = new ArgbColor(255, 255, 255, 255);
29 |
30 | #endregion
31 |
32 | #region Constructors
33 |
34 | public RandomDithering()
35 | : this(_whiteDefault, _blackDefault)
36 | { }
37 |
38 | public RandomDithering(ArgbColor white, ArgbColor black)
39 | : this(Environment.TickCount, white, black)
40 | { }
41 |
42 | public RandomDithering(int seed, ArgbColor white, ArgbColor black)
43 | {
44 | _random = new Random(seed);
45 | _white = white;
46 | _black = black;
47 | }
48 |
49 | public RandomDithering(int seed)
50 | : this(seed, _whiteDefault, _blackDefault)
51 | { }
52 |
53 | #endregion
54 |
55 | #region IErrorDiffusion Interface
56 |
57 | bool IErrorDiffusion.Prescan
58 | { get { return false; } }
59 |
60 | void IErrorDiffusion.Diffuse(ArgbColor[] data, ArgbColor original, ArgbColor transformed, int x, int y, int width, int height)
61 | {
62 | byte gray;
63 |
64 | gray = (byte)(0.299 * original.R + 0.587 * original.G + 0.114 * original.B);
65 |
66 | if (gray > _random.Next(0, 255))
67 | {
68 | data[y * width + x] = _white;
69 | }
70 | else
71 | {
72 | data[y * width + x] = _black;
73 | }
74 | }
75 |
76 | #endregion
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/ArgbColor.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.InteropServices;
2 |
3 | /* Dithering an image using the Floyd–Steinberg algorithm in C#
4 | * https://www.cyotek.com/blog/dithering-an-image-using-the-floyd-steinberg-algorithm-in-csharp
5 | *
6 | * Copyright © 2015-2017 Cyotek Ltd.
7 | *
8 | * Licensed under the MIT License. See LICENSE.txt for the full text.
9 | */
10 |
11 | namespace Cyotek.Drawing
12 | {
13 | ///
14 | /// Represents an ARGB (alpha, red, green, blue) color.
15 | ///
16 | /// The color of each pixel is represented as a 32-bit number: 8 bits each for alpha, red, green, and blue (ARGB). Each of the four components is a number from 0 through 255, with 0 representing no intensity and 255 representing full intensity. The alpha component specifies the transparency of the color: 0 is fully transparent, and 255 is fully opaque. To determine the alpha, red, green, or blue component of a color, use the A, R, G, or B property, respectively.
17 | [StructLayout(LayoutKind.Explicit)]
18 | public struct ArgbColor
19 | {
20 | [FieldOffset(0)]
21 | private readonly int _value;
22 |
23 | ///
24 | /// Gets the blue component value of this structure.
25 | ///
26 | [FieldOffset(0)]
27 | public byte B;
28 |
29 | ///
30 | /// Gets the green component value of this structure.
31 | ///
32 | [FieldOffset(1)]
33 | public byte G;
34 |
35 | ///
36 | /// Gets the red component value of this structure.
37 | ///
38 | [FieldOffset(2)]
39 | public byte R;
40 |
41 | ///
42 | /// Gets the alpha component value of this structure.
43 | ///
44 | [FieldOffset(3)]
45 | public byte A;
46 |
47 | public ArgbColor(int red, int green, int blue)
48 | : this(255, red, green, blue)
49 | { }
50 |
51 | public ArgbColor(int alpha, int red, int green, int blue)
52 | : this()
53 | {
54 | A = (byte)alpha;
55 | R = (byte)red;
56 | G = (byte)green;
57 | B = (byte)blue;
58 | }
59 |
60 | internal static ArgbColor FromArgb(byte a, byte r, byte g, byte b)
61 | {
62 | return new ArgbColor(a, r, g, b);
63 | }
64 |
65 | internal static ArgbColor FromArgb(byte r, byte g, byte b)
66 | {
67 | return new ArgbColor(r, g, b);
68 | }
69 |
70 | public int ToArgb()
71 | {
72 | return _value;
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/Dithering/OrderedDithering.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | // ReSharper disable once CheckNamespace
4 |
5 | namespace Cyotek.Drawing.Imaging.ColorReduction
6 | {
7 | public abstract class OrderedDithering : IErrorDiffusion
8 | {
9 | #region Constants
10 |
11 | private readonly byte[,] _matrix;
12 |
13 | private readonly byte _matrixHeight;
14 |
15 | private readonly byte _matrixWidth;
16 |
17 | #endregion
18 |
19 | #region Constructors
20 |
21 | protected OrderedDithering(byte[,] matrix)
22 | {
23 | int max;
24 | int scale;
25 |
26 | if (matrix == null)
27 | {
28 | throw new ArgumentNullException(nameof(matrix));
29 | }
30 |
31 | if (matrix.Length == 0)
32 | {
33 | throw new ArgumentException("Matrix is empty.", nameof(matrix));
34 | }
35 |
36 | _matrixWidth = (byte)(matrix.GetUpperBound(1) + 1);
37 | _matrixHeight = (byte)(matrix.GetUpperBound(0) + 1);
38 |
39 | max = _matrixWidth * _matrixHeight;
40 | scale = 255 / max;
41 |
42 | _matrix = new byte[_matrixHeight, _matrixWidth];
43 |
44 | for (int x = 0; x < _matrixWidth; x++)
45 | {
46 | for (int y = 0; y < _matrixHeight; y++)
47 | {
48 | _matrix[x, y] = Clamp(matrix[x, y] * scale);
49 | }
50 | }
51 |
52 | }
53 |
54 | #endregion
55 |
56 | #region Static Methods
57 |
58 | private static byte Clamp(int value)
59 | {
60 | byte result;
61 |
62 | if (value < 0)
63 | {
64 | result = 0;
65 | }
66 | else if (value > 255)
67 | {
68 | result = 255;
69 | }
70 | else
71 | {
72 | result = Convert.ToByte(value);
73 | }
74 |
75 | return result;
76 | }
77 |
78 | #endregion
79 |
80 | #region IErrorDiffusion Interface
81 |
82 | void IErrorDiffusion.Diffuse(ArgbColor[] data, ArgbColor original, ArgbColor transformed, int x, int y, int width, int height)
83 | {
84 | int row;
85 | int col;
86 | byte threshold;
87 |
88 | col = x % _matrixWidth;
89 | row = y % _matrixHeight;
90 |
91 | threshold = _matrix[col, row];
92 |
93 | if (threshold > 0)
94 | {
95 | byte r;
96 | byte g;
97 | byte b;
98 |
99 | r = Clamp(transformed.R + threshold);
100 | g = Clamp(transformed.G + threshold);
101 | b = Clamp(transformed.B + threshold);
102 |
103 | data[y * width + x] = ArgbColor.FromArgb(original.A, r, g, b);
104 | }
105 | }
106 |
107 | bool IErrorDiffusion.Prescan
108 | {
109 | get { return true; }
110 | }
111 |
112 | #endregion
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/Helpers/ClipboardHelpers.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Drawing;
3 | using System.IO;
4 | using System.Windows.Forms;
5 |
6 | /* Dithering an image using the Floyd–Steinberg algorithm in C#
7 | * https://www.cyotek.com/blog/dithering-an-image-using-the-floyd-steinberg-algorithm-in-csharp
8 | *
9 | * Copyright © 2015 Cyotek Ltd.
10 | *
11 | * Licensed under the MIT License. See LICENSE.txt for the full text.
12 | */
13 |
14 | namespace Cyotek.DitheringTest.Helpers
15 | {
16 | internal static class ClipboardHelpers
17 | {
18 | #region Constants
19 |
20 | public const string PngFormat = "PNG";
21 |
22 | #endregion
23 |
24 | #region Static Methods
25 |
26 | public static void CopyImage(Bitmap image)
27 | {
28 | if (image != null)
29 | {
30 | try
31 | {
32 | Clipboard.SetImage(image);
33 | }
34 | catch (Exception ex)
35 | {
36 | MessageBox.Show(string.Format("Failed to copy image. {0}", ex.GetBaseException().Message), "Copy Image", MessageBoxButtons.OK, MessageBoxIcon.Error);
37 | }
38 | }
39 | else
40 | {
41 | MessageBox.Show("Nothing to copy.", "Copy Image", MessageBoxButtons.OK, MessageBoxIcon.Information);
42 | }
43 | }
44 |
45 | public static Bitmap GetImage()
46 | {
47 | Bitmap result;
48 |
49 | // http://csharphelper.com/blog/2014/09/paste-a-png-format-image-with-a-transparent-background-from-the-clipboard-in-c/
50 |
51 | result = null;
52 |
53 | try
54 | {
55 | if (Clipboard.ContainsData(PngFormat))
56 | {
57 | object data;
58 |
59 | data = Clipboard.GetData(PngFormat);
60 |
61 | if (data != null)
62 | {
63 | Stream stream;
64 |
65 | stream = data as MemoryStream;
66 |
67 | if (stream == null)
68 | {
69 | byte[] buffer;
70 |
71 | buffer = data as byte[];
72 |
73 | if (buffer != null)
74 | {
75 | stream = new MemoryStream(buffer);
76 | }
77 | }
78 |
79 | if (stream != null)
80 | {
81 | result = Image.FromStream(stream).Copy();
82 |
83 | stream.Dispose();
84 | }
85 | }
86 | }
87 |
88 | if (result == null)
89 | {
90 | result = (Bitmap)Clipboard.GetImage();
91 | }
92 | }
93 | catch (Exception ex)
94 | {
95 | MessageBox.Show(string.Format("Failed to obtain image. {0}", ex.GetBaseException().Message), "Paste Image", MessageBoxButtons.OK, MessageBoxIcon.Error);
96 | }
97 |
98 | return result;
99 | }
100 |
101 | #endregion
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/Transforms/SimpleIndexedPalettePixelTransform.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Cyotek.Drawing;
3 |
4 | /* Finding nearest colors using Euclidean distance
5 | * https://www.cyotek.com/blog/finding-nearest-colors-using-euclidean-distance
6 | *
7 | * Copyright © 2017 Cyotek Ltd.
8 | */
9 |
10 | namespace Cyotek.DitheringTest.Transforms
11 | {
12 | internal abstract class SimpleIndexedPalettePixelTransform : IPixelTransform
13 | {
14 | #region Constants
15 |
16 | private readonly ArgbColor[] _map;
17 |
18 | private readonly IDictionary _mapLookup;
19 |
20 | #endregion
21 |
22 | #region Constructors
23 |
24 | protected SimpleIndexedPalettePixelTransform(ArgbColor[] map)
25 | {
26 | _map = map;
27 | _mapLookup = new Dictionary();
28 | }
29 |
30 | #endregion
31 |
32 | #region Methods
33 |
34 | private int FindNearestColor(ArgbColor current)
35 | {
36 | /*
37 | * sdist = 255L * 255L * 255L + 1L; // Compute the color
38 | * for (c=0; c= link.Start && position <= link.Start + link.Length)
66 | {
67 | result = true;
68 | break;
69 | }
70 | }
71 | }
72 |
73 | return result;
74 | }
75 |
76 | private void ossLinkLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
77 | {
78 | this.RunUrl((string)e.Link.LinkData);
79 | }
80 |
81 | private void RunUrl(string url)
82 | {
83 | try
84 | {
85 | Process.Start(url);
86 | }
87 | catch (Exception ex)
88 | {
89 | MessageBox.Show(ex.GetBaseException().Message, this.Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
90 | }
91 | }
92 |
93 | private void SetLink(LinkLabel control, string match, string url)
94 | {
95 | int index;
96 |
97 | index = -1;
98 |
99 | do
100 | {
101 | index = control.Text.IndexOf(match, index + 1, StringComparison.Ordinal);
102 | } while (index != -1 && this.HasLink(control.Links, index));
103 |
104 | control.Links.Add(index, match.Length, url);
105 | }
106 |
107 | private void webLinkLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
108 | {
109 | this.RunUrl("https://www.cyotek.com");
110 | }
111 |
112 | #endregion
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/Helpers/ImageUtilities.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 | using System.Drawing.Imaging;
3 | using Cyotek.Drawing;
4 |
5 | /* Dithering an image using the Floyd–Steinberg algorithm in C#
6 | * https://www.cyotek.com/blog/dithering-an-image-using-the-floyd-steinberg-algorithm-in-csharp
7 | *
8 | * Copyright © 2015 Cyotek Ltd.
9 | *
10 | * Licensed under the MIT License. See LICENSE.txt for the full text.
11 | */
12 |
13 | namespace Cyotek.DitheringTest.Helpers
14 | {
15 | internal static class ImageUtilities
16 | {
17 | #region Static Methods
18 |
19 | public static Bitmap Copy(this Image image)
20 | {
21 | Bitmap copy;
22 |
23 | copy = new Bitmap(image.Size.Width, image.Size.Height, PixelFormat.Format32bppArgb);
24 |
25 | using (Graphics g = Graphics.FromImage(copy))
26 | {
27 | g.Clear(Color.Transparent);
28 | g.PageUnit = GraphicsUnit.Pixel;
29 | g.DrawImage(image, new Rectangle(Point.Empty, image.Size));
30 | }
31 |
32 | return copy;
33 | }
34 |
35 | public static Bitmap ToBitmap(this ArgbColor[] data, Size size)
36 | {
37 | int height;
38 | int width;
39 | BitmapData bitmapData;
40 | Bitmap result;
41 |
42 | // Based on code from http://blog.biophysengr.net/2011/11/rapid-bitmap-access-using-unsafe-code.html
43 |
44 | result = new Bitmap(size.Width, size.Height, PixelFormat.Format32bppArgb);
45 | width = result.Width;
46 | height = result.Height;
47 |
48 | // Lock the entire bitmap
49 | bitmapData = result.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
50 |
51 | //Enter unsafe mode so that we can use pointers
52 | unsafe
53 | {
54 | ArgbColor* pixelPtr;
55 |
56 | // Get a pointer to the beginning of the pixel data region
57 | // The upper-left corner
58 | pixelPtr = (ArgbColor*)bitmapData.Scan0;
59 |
60 | // Iterate through rows and columns
61 | for (int row = 0; row < size.Height; row++)
62 | {
63 | for (int col = 0; col < size.Width; col++)
64 | {
65 | int index;
66 | ArgbColor color;
67 |
68 | index = row * size.Width + col;
69 | color = data[index];
70 |
71 | // Set the pixel (fast!)
72 | *pixelPtr = color;
73 |
74 | // Update the pointer
75 | pixelPtr++;
76 | }
77 | }
78 | }
79 |
80 | // Unlock the bitmap
81 | result.UnlockBits(bitmapData);
82 |
83 | return result;
84 | }
85 |
86 | internal static ArgbColor[] GetPixelsFrom32BitArgbImage(this Bitmap bitmap)
87 | {
88 | int width;
89 | int height;
90 | BitmapData bitmapData;
91 | ArgbColor[] results;
92 |
93 | // NOTE: As the name should give a hint, it only supports 32bit ARGB images.
94 | // Don't rely on this for production, it needs expanding to support multiple other types
95 |
96 | width = bitmap.Width;
97 | height = bitmap.Height;
98 | results = new ArgbColor[width * height];
99 | bitmapData = bitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
100 |
101 | unsafe
102 | {
103 | ArgbColor* pixel;
104 |
105 | pixel = (ArgbColor*)bitmapData.Scan0;
106 |
107 | for (int row = 0; row < height; row++)
108 | {
109 | for (int col = 0; col < width; col++)
110 | {
111 | results[row * width + col] = *pixel;
112 |
113 | pixel++;
114 | }
115 | }
116 | }
117 |
118 | bitmap.UnlockBits(bitmapData);
119 |
120 | return results;
121 | }
122 |
123 | #endregion
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/Dithering/ErrorDiffusionDithering.cs:
--------------------------------------------------------------------------------
1 | /* Even more algorithms for dithering images using C#
2 | * https://www.cyotek.com/blog/even-more-algorithms-for-dithering-images-using-csharp
3 | *
4 | * Copyright © 2015 Cyotek Ltd.
5 | *
6 | * Licensed under the MIT License. See LICENSE.txt for the full text.
7 | */
8 |
9 | using System;
10 |
11 | namespace Cyotek.Drawing.Imaging.ColorReduction
12 | {
13 | public abstract class ErrorDiffusionDithering : IErrorDiffusion
14 | {
15 | #region Constants
16 |
17 | private readonly byte _divisor;
18 |
19 | private readonly byte[,] _matrix;
20 |
21 | private readonly byte _matrixHeight;
22 |
23 | private readonly byte _matrixWidth;
24 |
25 | private readonly byte _startingOffset;
26 |
27 | private readonly bool _useShifting;
28 |
29 | #endregion
30 |
31 | #region Constructors
32 |
33 | protected ErrorDiffusionDithering(byte[,] matrix, byte divisor, bool useShifting)
34 | {
35 | if (matrix == null)
36 | {
37 | throw new ArgumentNullException(nameof(matrix));
38 | }
39 |
40 | if (matrix.Length == 0)
41 | {
42 | throw new ArgumentException("Matrix is empty.", nameof(matrix));
43 | }
44 |
45 | _matrix = matrix;
46 | _matrixWidth = (byte)(matrix.GetUpperBound(1) + 1);
47 | _matrixHeight = (byte)(matrix.GetUpperBound(0) + 1);
48 | _divisor = divisor;
49 | _useShifting = useShifting;
50 |
51 | for (int i = 0; i < _matrixWidth; i++)
52 | {
53 | if (matrix[0, i] != 0)
54 | {
55 | _startingOffset = (byte)(i - 1);
56 | break;
57 | }
58 | }
59 | }
60 |
61 | #endregion
62 |
63 | #region IErrorDiffusion Interface
64 |
65 | bool IErrorDiffusion.Prescan
66 | { get { return false; } }
67 |
68 | void IErrorDiffusion.Diffuse(ArgbColor[] data, ArgbColor original, ArgbColor transformed, int x, int y, int width, int height)
69 | {
70 | int redError;
71 | int blueError;
72 | int greenError;
73 |
74 | redError = original.R - transformed.R;
75 | greenError = original.G - transformed.G;
76 | blueError = original.B - transformed.B;
77 |
78 | for (int row = 0; row < _matrixHeight; row++)
79 | {
80 | int offsetY;
81 |
82 | offsetY = y + row;
83 |
84 | for (int col = 0; col < _matrixWidth; col++)
85 | {
86 | int coefficient;
87 | int offsetX;
88 |
89 | coefficient = _matrix[row, col];
90 | offsetX = x + (col - _startingOffset);
91 |
92 | if (coefficient != 0 && offsetX > 0 && offsetX < width && offsetY > 0 && offsetY < height)
93 | {
94 | ArgbColor offsetPixel;
95 | int offsetIndex;
96 | int newR;
97 | int newG;
98 | int newB;
99 | byte r;
100 | byte g;
101 | byte b;
102 |
103 | offsetIndex = offsetY * width + offsetX;
104 | offsetPixel = data[offsetIndex];
105 |
106 | // if the UseShifting property is set, then bit shift the values by the specified
107 | // divisor as this is faster than integer division. Otherwise, use integer division
108 |
109 | if (_useShifting)
110 | {
111 | newR = (redError * coefficient) >> _divisor;
112 | newG = (greenError * coefficient) >> _divisor;
113 | newB = (blueError * coefficient) >> _divisor;
114 | }
115 | else
116 | {
117 | newR = redError * coefficient / _divisor;
118 | newG = greenError * coefficient / _divisor;
119 | newB = blueError * coefficient / _divisor;
120 | }
121 |
122 | r = (offsetPixel.R + newR).ToByte();
123 | g = (offsetPixel.G + newG).ToByte();
124 | b = (offsetPixel.B + newB).ToByte();
125 |
126 | data[offsetIndex] = ArgbColor.FromArgb(offsetPixel.A, r, g, b);
127 | }
128 | }
129 | }
130 | }
131 |
132 | #endregion
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace Cyotek.DitheringTest.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Cyotek.DitheringTest.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// Looks up a localized resource of type System.Drawing.Bitmap.
65 | ///
66 | internal static System.Drawing.Bitmap ActualSize {
67 | get {
68 | object obj = ResourceManager.GetObject("ActualSize", resourceCulture);
69 | return ((System.Drawing.Bitmap)(obj));
70 | }
71 | }
72 |
73 | ///
74 | /// Looks up a localized resource of type System.Drawing.Bitmap.
75 | ///
76 | internal static System.Drawing.Bitmap Copy {
77 | get {
78 | object obj = ResourceManager.GetObject("Copy", resourceCulture);
79 | return ((System.Drawing.Bitmap)(obj));
80 | }
81 | }
82 |
83 | ///
84 | /// Looks up a localized resource of type System.Drawing.Bitmap.
85 | ///
86 | internal static System.Drawing.Bitmap CopyMerged {
87 | get {
88 | object obj = ResourceManager.GetObject("CopyMerged", resourceCulture);
89 | return ((System.Drawing.Bitmap)(obj));
90 | }
91 | }
92 |
93 | ///
94 | /// Looks up a localized resource of type System.Drawing.Bitmap.
95 | ///
96 | internal static System.Drawing.Bitmap HorizontalSplit {
97 | get {
98 | object obj = ResourceManager.GetObject("HorizontalSplit", resourceCulture);
99 | return ((System.Drawing.Bitmap)(obj));
100 | }
101 | }
102 |
103 | ///
104 | /// Looks up a localized resource of type System.Drawing.Bitmap.
105 | ///
106 | internal static System.Drawing.Bitmap Open {
107 | get {
108 | object obj = ResourceManager.GetObject("Open", resourceCulture);
109 | return ((System.Drawing.Bitmap)(obj));
110 | }
111 | }
112 |
113 | ///
114 | /// Looks up a localized resource of type System.Drawing.Bitmap.
115 | ///
116 | internal static System.Drawing.Bitmap Paste {
117 | get {
118 | object obj = ResourceManager.GetObject("Paste", resourceCulture);
119 | return ((System.Drawing.Bitmap)(obj));
120 | }
121 | }
122 |
123 | ///
124 | /// Looks up a localized resource of type System.Drawing.Bitmap.
125 | ///
126 | internal static System.Drawing.Bitmap Save {
127 | get {
128 | object obj = ResourceManager.GetObject("Save", resourceCulture);
129 | return ((System.Drawing.Bitmap)(obj));
130 | }
131 | }
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/Line.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using System.Drawing;
4 | using System.Windows.Forms;
5 | using Cyotek.Windows.Forms.Design;
6 |
7 | namespace Cyotek.Windows.Forms
8 | {
9 | [Designer(typeof(LineDesigner))]
10 | public class Line : Control
11 | {
12 | #region Instance Fields
13 |
14 | private FlatStyle _flatStyle = FlatStyle.Standard;
15 |
16 | private Color _lineColor;
17 |
18 | private Orientation _orientation;
19 |
20 | #endregion
21 |
22 | #region Public Constructors
23 |
24 | public Line()
25 | {
26 | this.SetStyle(ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.ResizeRedraw | ControlStyles.UserPaint, true);
27 | this.SetStyle(ControlStyles.Selectable, false);
28 | this.LineColor = SystemColors.ControlDark;
29 | }
30 |
31 | #endregion
32 |
33 | #region Events
34 |
35 | public event EventHandler FlatStyleChanged;
36 |
37 | ///
38 | /// Occurs when the LineColor property value changes
39 | ///
40 | [Category("Property Changed")]
41 | public event EventHandler LineColorChanged;
42 |
43 | ///
44 | /// Occurs when the Orientation property value changes
45 | ///
46 | [Category("Property Changed")]
47 | public event EventHandler OrientationChanged;
48 |
49 | #endregion
50 |
51 | #region Overridden Properties
52 |
53 | protected override Size DefaultSize
54 | {
55 | get { return new Size(100, 2); }
56 | }
57 |
58 | #endregion
59 |
60 | #region Overridden Methods
61 |
62 | protected override void OnPaint(PaintEventArgs pe)
63 | {
64 | int x1;
65 | int y1;
66 | int x2;
67 | int y2;
68 | int xOffset;
69 | int yOffset;
70 |
71 | switch (this.Orientation)
72 | {
73 | case Orientation.Horizontal:
74 | x1 = 0;
75 | y1 = (this.Height / 2) - 1;
76 | x2 = this.Width;
77 | y2 = y1;
78 | xOffset = 0;
79 | yOffset = 1;
80 | break;
81 | default:
82 | x1 = (this.Width / 2) - 1;
83 | y1 = 0;
84 | x2 = x1;
85 | y2 = this.Height;
86 | xOffset = 1;
87 | yOffset = 0;
88 | break;
89 | }
90 |
91 | switch (this.FlatStyle)
92 | {
93 | case FlatStyle.System:
94 | using (Pen pen = new Pen(this.LineColor))
95 | {
96 | pe.Graphics.DrawLine(pen, x1, y1, x2, y2);
97 | }
98 | break;
99 | default:
100 | pe.Graphics.DrawLine(SystemPens.ControlDark, x1, y1, x2, y2);
101 | pe.Graphics.DrawLine(SystemPens.ControlLightLight, x1 + xOffset, y1 + yOffset, x2 + xOffset, y2 + yOffset);
102 | break;
103 | }
104 | }
105 |
106 | protected override void OnSystemColorsChanged(EventArgs e)
107 | {
108 | base.OnSystemColorsChanged(e);
109 |
110 | this.Invalidate();
111 | }
112 |
113 | #endregion
114 |
115 | #region Public Properties
116 |
117 | [Category("Appearance")]
118 | [DefaultValue(typeof(FlatStyle), "Standard")]
119 | public FlatStyle FlatStyle
120 | {
121 | get { return _flatStyle; }
122 | set
123 | {
124 | if (_flatStyle != value)
125 | {
126 | _flatStyle = value;
127 |
128 | this.OnFlatStyleChanged(EventArgs.Empty);
129 | }
130 | }
131 | }
132 |
133 | [Category("Appearance")]
134 | [DefaultValue(typeof(Color), "ControlDark")]
135 | public Color LineColor
136 | {
137 | get { return _lineColor; }
138 | set
139 | {
140 | if (this.LineColor != value)
141 | {
142 | _lineColor = value;
143 |
144 | this.OnLineColorChanged(EventArgs.Empty);
145 | }
146 | }
147 | }
148 |
149 | [Category("Appearance")]
150 | [DefaultValue(typeof(Orientation), "Horizontal")]
151 | public Orientation Orientation
152 | {
153 | get { return _orientation; }
154 | set
155 | {
156 | if (this.Orientation != value)
157 | {
158 | _orientation = value;
159 |
160 | this.OnOrientationChanged(EventArgs.Empty);
161 | }
162 | }
163 | }
164 |
165 | #endregion
166 |
167 | #region Protected Members
168 |
169 | ///
170 | /// Raises the event.
171 | ///
172 | /// The instance containing the event data.
173 | protected virtual void OnFlatStyleChanged(EventArgs e)
174 | {
175 | EventHandler handler;
176 |
177 | handler = this.FlatStyleChanged;
178 |
179 | if (handler != null)
180 | {
181 | handler(this, e);
182 | }
183 | }
184 |
185 | ///
186 | /// Raises the event.
187 | ///
188 | /// The instance containing the event data.
189 | protected virtual void OnLineColorChanged(EventArgs e)
190 | {
191 | EventHandler handler;
192 |
193 | this.Invalidate();
194 |
195 | handler = this.LineColorChanged;
196 |
197 | if (handler != null)
198 | {
199 | handler(this, e);
200 | }
201 | }
202 |
203 | ///
204 | /// Raises the event.
205 | ///
206 | /// The instance containing the event data.
207 | protected virtual void OnOrientationChanged(EventArgs e)
208 | {
209 | EventHandler handler;
210 |
211 | this.Invalidate();
212 |
213 | handler = this.OrientationChanged;
214 |
215 | if (handler != null)
216 | {
217 | handler(this, e);
218 | }
219 | }
220 |
221 | #endregion
222 | }
223 | }
224 |
--------------------------------------------------------------------------------
/src/AboutDialog.designer.cs:
--------------------------------------------------------------------------------
1 | namespace Cyotek.DitheringTest
2 | {
3 | partial class AboutDialog
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Windows Form Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(AboutDialog));
32 | this.nameLabel = new System.Windows.Forms.Label();
33 | this.copyrightLabel = new System.Windows.Forms.Label();
34 | this.webLinkLabel = new System.Windows.Forms.LinkLabel();
35 | this.closeButton = new System.Windows.Forms.Button();
36 | this.ossLinkLabel = new System.Windows.Forms.LinkLabel();
37 | this.SuspendLayout();
38 | //
39 | // nameLabel
40 | //
41 | this.nameLabel.AutoSize = true;
42 | this.nameLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
43 | this.nameLabel.Location = new System.Drawing.Point(12, 9);
44 | this.nameLabel.Name = "nameLabel";
45 | this.nameLabel.Size = new System.Drawing.Size(61, 13);
46 | this.nameLabel.TabIndex = 0;
47 | this.nameLabel.Text = "AppName";
48 | //
49 | // copyrightLabel
50 | //
51 | this.copyrightLabel.AutoSize = true;
52 | this.copyrightLabel.Location = new System.Drawing.Point(12, 22);
53 | this.copyrightLabel.Name = "copyrightLabel";
54 | this.copyrightLabel.Size = new System.Drawing.Size(51, 13);
55 | this.copyrightLabel.TabIndex = 1;
56 | this.copyrightLabel.Text = "Copyright";
57 | //
58 | // webLinkLabel
59 | //
60 | this.webLinkLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
61 | this.webLinkLabel.AutoSize = true;
62 | this.webLinkLabel.Location = new System.Drawing.Point(12, 233);
63 | this.webLinkLabel.Name = "webLinkLabel";
64 | this.webLinkLabel.Size = new System.Drawing.Size(120, 13);
65 | this.webLinkLabel.TabIndex = 2;
66 | this.webLinkLabel.TabStop = true;
67 | this.webLinkLabel.Text = "https://www.cyotek.com";
68 | this.webLinkLabel.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.webLinkLabel_LinkClicked);
69 | //
70 | // closeButton
71 | //
72 | this.closeButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
73 | this.closeButton.DialogResult = System.Windows.Forms.DialogResult.Cancel;
74 | this.closeButton.Location = new System.Drawing.Point(420, 228);
75 | this.closeButton.Name = "closeButton";
76 | this.closeButton.Size = new System.Drawing.Size(75, 23);
77 | this.closeButton.TabIndex = 3;
78 | this.closeButton.Text = "Close";
79 | this.closeButton.UseVisualStyleBackColor = true;
80 | this.closeButton.Click += new System.EventHandler(this.closeButton_Click);
81 | //
82 | // ossLinkLabel
83 | //
84 | this.ossLinkLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
85 | | System.Windows.Forms.AnchorStyles.Left)
86 | | System.Windows.Forms.AnchorStyles.Right)));
87 | this.ossLinkLabel.Location = new System.Drawing.Point(12, 70);
88 | this.ossLinkLabel.Name = "ossLinkLabel";
89 | this.ossLinkLabel.Size = new System.Drawing.Size(483, 155);
90 | this.ossLinkLabel.TabIndex = 4;
91 | this.ossLinkLabel.TabStop = true;
92 | this.ossLinkLabel.Text = resources.GetString("ossLinkLabel.Text");
93 | this.ossLinkLabel.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.ossLinkLabel_LinkClicked);
94 | //
95 | // AboutDialog
96 | //
97 | this.AcceptButton = this.closeButton;
98 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
99 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
100 | this.CancelButton = this.closeButton;
101 | this.ClientSize = new System.Drawing.Size(507, 263);
102 | this.Controls.Add(this.ossLinkLabel);
103 | this.Controls.Add(this.closeButton);
104 | this.Controls.Add(this.webLinkLabel);
105 | this.Controls.Add(this.copyrightLabel);
106 | this.Controls.Add(this.nameLabel);
107 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
108 | this.MaximizeBox = false;
109 | this.MinimizeBox = false;
110 | this.Name = "AboutDialog";
111 | this.ShowIcon = false;
112 | this.ShowInTaskbar = false;
113 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
114 | this.Text = "About";
115 | this.ResumeLayout(false);
116 | this.PerformLayout();
117 |
118 | }
119 |
120 | #endregion
121 |
122 | private System.Windows.Forms.Label nameLabel;
123 | private System.Windows.Forms.Label copyrightLabel;
124 | private System.Windows.Forms.LinkLabel webLinkLabel;
125 | private System.Windows.Forms.Button closeButton;
126 | private System.Windows.Forms.LinkLabel ossLinkLabel;
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/AboutDialog.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 | Most of the dithering algorithms and the nearest colour algorithm featured in this program were taken from DHALF.TXT.
122 |
123 | Cyotek has published an article on calculating colour distance.
124 |
125 | The ImageBox control is available as open source on GitHub, with articles describing its use and how it was developed on cyotek.com. The GroupBox control can be obtained from another article on cyotek.com.
126 |
127 | Please see README.md for additional resources.
128 |
129 |
--------------------------------------------------------------------------------
/src/DitheringTest.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {5192C96D-2219-41FB-A94D-617900FDDBDC}
8 | WinExe
9 | Properties
10 | Cyotek.DitheringTest
11 | dithering
12 | v4.5
13 | 512
14 | ..\
15 |
16 |
17 |
18 | AnyCPU
19 | true
20 | full
21 | false
22 | bin\Debug\
23 | DEBUG;TRACE
24 | prompt
25 | 4
26 | true
27 |
28 |
29 | AnyCPU
30 | pdbonly
31 | true
32 | bin\Release\
33 | TRACE
34 | prompt
35 | 4
36 | true
37 |
38 |
39 | ..\resources\icon.ico
40 |
41 |
42 |
43 | ..\packages\CyotekImageBox.1.1.5.1\lib\net20\Cyotek.Windows.Forms.ImageBox.dll
44 | True
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | Form
55 |
56 |
57 | AboutDialog.cs
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | Component
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | Component
85 |
86 |
87 | Form
88 |
89 |
90 | MainForm.cs
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 | AboutDialog.cs
104 |
105 |
106 | MainForm.cs
107 |
108 |
109 | ResXFileCodeGenerator
110 | Resources.Designer.cs
111 | Designer
112 |
113 |
114 | True
115 | Resources.resx
116 | True
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 | icon.ico
132 |
133 |
134 |
135 |
136 |
137 |
138 | PreserveNewest
139 |
140 |
141 |
142 |
143 |
150 |
--------------------------------------------------------------------------------
/src/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 |
122 | ..\resources\image-resize-actual.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
123 |
124 |
125 | ..\resources\copy.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
126 |
127 |
128 | ..\resources\copy and append.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
129 |
130 |
131 | ..\Resources\ui-splitter-horizontal.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
132 |
133 |
134 | ..\Resources\Open.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
135 |
136 |
137 | ..\resources\paste.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
138 |
139 |
140 | ..\Resources\Save.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
141 |
142 |
--------------------------------------------------------------------------------
/src/MainForm.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 | 17, 17
122 |
123 |
124 | 137, 17
125 |
126 |
127 | 247, 17
128 |
129 |
130 | 361, 17
131 |
132 |
133 |
134 |
135 | AAABAAQAMDACAAEAAQAwAwAARgAAACAgAgABAAEAMAEAAHYDAAAYGAIAAQABAPAAAACmBAAAEBACAAEA
136 | AQCwAAAAlgUAACgAAAAwAAAAYAAAAAEAAQAAAAAAgAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////AAAA
137 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAIiIiIiIiAAAAAAAAAAAAAAgICAgICAAAAAAAAAAAAAAiIiIiIiI
138 | AAAAAAAAAAAAACIiIiIiIgAAAAAAAAAAAACIiIiIiIgAAAAAAAAAAAAAqqqqqqqqAAAAAAAAAAAAAKio
139 | qKioqAAAAAAAAAAAAACqqqqqqqoAAAAAAAAAAAAAqqqqqqqqAAAAAAAAAAAAAKqqqqqqqgAAQEBAQEBA
140 | AACqqqqqqqoAAAAAAAAAAAAAqqqqqqqqAABEREREREQAAKqqqqqqqgAAUVFRUVFRAACqqqqqqqoAAERE
141 | RERERAAAqqqqqqqqAABVVVVVVVUAAKqqqqqqqgAA3d3d3d3dAACqqqqqqqoAAHV1dXV1dQAAqqqqqqqq
142 | AADd3d3d3d0AAKqqqqqqqgAA////////AACqqqqqqqoAAP///////wAArq6urq6uAAD///////8AALq6
143 | urq6ugAA////////AADu7u7u7u4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
144 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
145 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
146 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
147 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
148 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
149 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAAAAgAAAAQAAAAAEA
150 | AQAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAIiIiIgAAAAAIiIiIgAA
151 | AACIiIiIAAAAAKqqqqoAAAAAioqKigAAAACqqqqqAAAAAKqqqqoAAAAAqqqqqkRERESqqqqqVVVVVaqq
152 | qqrV1dXVqqqqqnd3d3eqqqqq3d3d3aqqqqr/////qqqqqv/////u7u7uAAAAAAAAAAAAAAAAAAAAAAAA
153 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
154 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAGAAAADAA
155 | AAABAAEAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wAAAAAAAAAAAAAAAACIiIgAAAAAAKKi
156 | ogAAAAAAiIiIAAAAAACqqqoAAAAAAKqqqgAAAAAAqqqqAERERACqqqoAVVVVAKqqqgDd3d0AqqqqAP//
157 | /wCqqqoA////AO7u7gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
158 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAEAAAACAA
159 | AAABAAEAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wAAAAAAAgIAAAAAAACIiAAAAAAAAKqq
160 | AAAAAAAAqqoAAAAAAACqqgAAVVUAAKqqAAD39wAAqqoAAP//AADu7gAAAAAAAAAAAAAAAAAAAAAAAAAA
161 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
162 |
163 |
164 |
--------------------------------------------------------------------------------
/src/GroupBox.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using System.ComponentModel.Design;
4 | using System.Drawing;
5 | using System.Drawing.Drawing2D;
6 | using System.Windows.Forms;
7 |
8 | namespace Cyotek.Windows.Forms
9 | {
10 | // Cyotek GroupBox Component
11 | // www.cyotek.com
12 |
13 | ///
14 | /// Represents a Windows control that displays a frame at the top of a group of controls with an optional caption and icon.
15 | ///
16 | [ToolboxItem(true)]
17 | [DefaultEvent("Click")]
18 | [DefaultProperty("Text")]
19 | [Designer("System.Windows.Forms.Design.GroupBoxDesigner, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
20 | [Designer("System.Windows.Forms.Design.DocumentDesigner, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(IRootDesigner))]
21 | internal class GroupBox : System.Windows.Forms.GroupBox
22 | {
23 | #region Instance Fields
24 |
25 | private Border3DSide _borders = Border3DSide.Top;
26 |
27 | private Pen _bottomPen;
28 |
29 | private Color _headerForeColor;
30 |
31 | private Size _iconMargin;
32 |
33 | private Image _image;
34 |
35 | private Color _lineColorBottom;
36 |
37 | private Color _lineColorTop;
38 |
39 | private bool _showBorders;
40 |
41 | private Pen _topPen;
42 |
43 | #endregion
44 |
45 | #region Public Constructors
46 |
47 | ///
48 | /// Initializes a new instance of the class.
49 | ///
50 | public GroupBox()
51 | {
52 | _showBorders = true;
53 | _iconMargin = new Size(0, 6);
54 | _lineColorBottom = SystemColors.ButtonHighlight;
55 | _lineColorTop = SystemColors.ButtonShadow;
56 | _headerForeColor = SystemColors.HotTrack;
57 |
58 | this.SetStyle(ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.ResizeRedraw | ControlStyles.UserPaint | ControlStyles.SupportsTransparentBackColor, true);
59 |
60 | this.CreateResources();
61 | }
62 |
63 | #endregion
64 |
65 | #region Overridden Properties
66 |
67 | ///
68 | /// Gets a rectangle that represents the dimensions of the .
69 | ///
70 | ///
71 | ///
72 | /// A with the dimensions of the .
73 | ///
74 | public override Rectangle DisplayRectangle
75 | {
76 | get
77 | {
78 | Size clientSize;
79 | int fontHeight;
80 | int imageSize;
81 |
82 | clientSize = base.ClientSize;
83 | fontHeight = this.Font.Height;
84 |
85 | if (_image != null)
86 | {
87 | imageSize = _iconMargin.Width + _image.Width + 3;
88 | }
89 | else
90 | {
91 | imageSize = 0;
92 | }
93 |
94 | return new Rectangle(3 + imageSize, fontHeight + 3, Math.Max(clientSize.Width - (imageSize + 6), 0), Math.Max((clientSize.Height - fontHeight) - 6, 0));
95 | }
96 | }
97 |
98 | ///
99 | /// Returns or sets the text displayed in this control.
100 | ///
101 | ///
102 | ///
103 | /// The text associated with this control.
104 | ///
105 | [Browsable(true)]
106 | [DefaultValue("")]
107 | public override string Text
108 | {
109 | get { return base.Text; }
110 | set
111 | {
112 | base.Text = value;
113 | this.Invalidate();
114 | }
115 | }
116 |
117 | #endregion
118 |
119 | #region Overridden Methods
120 |
121 | ///
122 | /// Clean up any resources being used.
123 | ///
124 | /// true if managed resources should be disposed; otherwise, false.
125 | protected override void Dispose(bool disposing)
126 | {
127 | if (disposing)
128 | {
129 | this.CleanUpResources();
130 | }
131 |
132 | base.Dispose(disposing);
133 | }
134 |
135 | ///
136 | /// Occurs when the control is to be painted.
137 | ///
138 | /// A that contains the event data.
139 | protected override void OnPaint(PaintEventArgs e)
140 | {
141 | SizeF size;
142 | int y;
143 | TextFormatFlags flags;
144 | Rectangle textBounds;
145 |
146 | flags = TextFormatFlags.WordEllipsis | TextFormatFlags.NoPrefix | TextFormatFlags.Left | TextFormatFlags.SingleLine;
147 |
148 | size = TextRenderer.MeasureText(e.Graphics, this.Text, this.Font, this.ClientSize, flags);
149 | y = (int)(size.Height + 3) / 2;
150 | textBounds = new Rectangle(1, 1, (int)size.Width, (int)size.Height);
151 |
152 | if (this.ShowBorders)
153 | {
154 | if ((_borders & Border3DSide.All) == Border3DSide.All)
155 | {
156 | e.Graphics.DrawRectangle(_bottomPen, 1, y + 1, this.Width - 2, this.Height - (y + 2));
157 | e.Graphics.DrawRectangle(_topPen, 0, y, this.Width - 2, this.Height - (y + 2));
158 | }
159 | else
160 | {
161 | if ((_borders & Border3DSide.Top) == Border3DSide.Top)
162 | {
163 | e.Graphics.DrawLine(_topPen, size.Width + 3, y, this.Width - 5, y);
164 | e.Graphics.DrawLine(_bottomPen, size.Width + 3, y + 1, this.Width - 5, y + 1);
165 | }
166 |
167 | if ((_borders & Border3DSide.Left) == Border3DSide.Left)
168 | {
169 | e.Graphics.DrawLine(_topPen, 0, y, 0, this.Height - 1);
170 | e.Graphics.DrawLine(_bottomPen, 1, y, 1, this.Height - 1);
171 | }
172 |
173 | if ((_borders & Border3DSide.Right) == Border3DSide.Right)
174 | {
175 | e.Graphics.DrawLine(_bottomPen, this.Width - 1, y, this.Width - 1, this.Height - 1);
176 | e.Graphics.DrawLine(_topPen, this.Width - 2, y, this.Width - 2, this.Height - 1);
177 | }
178 |
179 | if ((_borders & Border3DSide.Bottom) == Border3DSide.Bottom)
180 | {
181 | e.Graphics.DrawLine(_topPen, 0, this.Height - 2, this.Width, this.Height - 2);
182 | e.Graphics.DrawLine(_bottomPen, 0, this.Height - 1, this.Width, this.Height - 1);
183 | }
184 | }
185 | }
186 |
187 | // header text
188 | TextRenderer.DrawText(e.Graphics, this.Text, this.Font, textBounds, this.HeaderForeColor, flags);
189 |
190 | // draw the image
191 | if ((_image != null))
192 | {
193 | e.Graphics.DrawImage(_image, this.Padding.Left + _iconMargin.Width, this.Padding.Top + (int)size.Height + _iconMargin.Height, _image.Width, _image.Height);
194 | }
195 |
196 | //draw a designtime outline
197 | if (this.DesignMode && (_borders & Border3DSide.All) != Border3DSide.All)
198 | {
199 | using (Pen pen = new Pen(SystemColors.ButtonShadow))
200 | {
201 | pen.DashStyle = DashStyle.Dot;
202 | e.Graphics.DrawRectangle(pen, 0, 0, Width - 1, Height - 1);
203 | }
204 | }
205 | }
206 |
207 | ///
208 | /// Raises the event.
209 | ///
210 | /// An that contains the event data.
211 | protected override void OnSystemColorsChanged(EventArgs e)
212 | {
213 | base.OnSystemColorsChanged(e);
214 |
215 | this.CreateResources();
216 | this.Invalidate();
217 | }
218 |
219 | #endregion
220 |
221 | #region Public Properties
222 |
223 | [Category("Appearance")]
224 | [DefaultValue(typeof(Border3DSide), "Top")]
225 | public Border3DSide Borders
226 | {
227 | get { return _borders; }
228 | set
229 | {
230 | _borders = value;
231 | this.Invalidate();
232 | }
233 | }
234 |
235 | [Category("Appearance")]
236 | [DefaultValue(typeof(Color), "HotTrack")]
237 | public Color HeaderForeColor
238 | {
239 | get { return _headerForeColor; }
240 | set
241 | {
242 | _headerForeColor = value;
243 | this.CreateResources();
244 | this.Invalidate();
245 | }
246 | }
247 |
248 | ///
249 | /// Gets or sets the icon margin.
250 | ///
251 | /// The icon margin.
252 | [Category("Appearance")]
253 | [DefaultValue(typeof(Size), "0, 6")]
254 | public Size IconMargin
255 | {
256 | get { return _iconMargin; }
257 | set
258 | {
259 | _iconMargin = value;
260 | this.Invalidate();
261 | }
262 | }
263 |
264 | ///
265 | /// Gets or sets the image to display.
266 | ///
267 | /// The image to display.
268 | [Browsable(true)]
269 | [Category("Appearance")]
270 | [DefaultValue(typeof(Image), "")]
271 | public Image Image
272 | {
273 | get { return _image; }
274 | set
275 | {
276 | _image = value;
277 | this.Invalidate();
278 | }
279 | }
280 |
281 | ///
282 | /// Gets or sets the line color bottom.
283 | ///
284 | /// The line color bottom.
285 | [Browsable(true)]
286 | [Category("Appearance")]
287 | [DefaultValue(typeof(Color), "ButtonHighlight")]
288 | public Color LineColorBottom
289 | {
290 | get { return _lineColorBottom; }
291 | set
292 | {
293 | _lineColorBottom = value;
294 | this.CreateResources();
295 | this.Invalidate();
296 | }
297 | }
298 |
299 | ///
300 | /// Gets or sets the line color top.
301 | ///
302 | /// The line color top.
303 | [Browsable(true)]
304 | [Category("Appearance")]
305 | [DefaultValue(typeof(Color), "ButtonShadow")]
306 | public Color LineColorTop
307 | {
308 | get { return _lineColorTop; }
309 | set
310 | {
311 | _lineColorTop = value;
312 | this.CreateResources();
313 | this.Invalidate();
314 | }
315 | }
316 |
317 | [DefaultValue(true)]
318 | [Category("Appearance")]
319 | public bool ShowBorders
320 | {
321 | get { return _showBorders; }
322 | set
323 | {
324 | _showBorders = value;
325 | this.Invalidate();
326 | }
327 | }
328 |
329 | #endregion
330 |
331 | #region Private Members
332 |
333 | ///
334 | /// Cleans up GDI resources.
335 | ///
336 | private void CleanUpResources()
337 | {
338 | if (_topPen != null)
339 | {
340 | _topPen.Dispose();
341 | }
342 |
343 | if (_bottomPen != null)
344 | {
345 | _bottomPen.Dispose();
346 | }
347 | }
348 |
349 | ///
350 | /// Creates GDI resources.
351 | ///
352 | private void CreateResources()
353 | {
354 | this.CleanUpResources();
355 |
356 | _topPen = new Pen(_lineColorTop);
357 | _bottomPen = new Pen(_lineColorBottom);
358 | }
359 |
360 | #endregion
361 | }
362 | }
363 |
--------------------------------------------------------------------------------
/src/Helpers/ArticleDiagrams.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 | using System.Drawing.Drawing2D;
3 | using System.Drawing.Text;
4 |
5 | /* Dithering an image using the Floyd–Steinberg algorithm in C#
6 | * https://www.cyotek.com/blog/dithering-an-image-using-the-floyd-steinberg-algorithm-in-csharp
7 | *
8 | * Copyright © 2015 Cyotek Ltd.
9 | *
10 | * Licensed under the MIT License. See LICENSE.txt for the full text.
11 | */
12 |
13 | namespace Cyotek.DitheringTest.Helpers
14 | {
15 | internal static class ArticleDiagrams
16 | {
17 | #region Static Methods
18 |
19 | public static Bitmap CreateBurkesDiagram()
20 | {
21 | Bitmap bitmap;
22 | int w;
23 | int h;
24 | int cellSize;
25 | int rows;
26 | int cols;
27 | int processedCells;
28 | int cell;
29 | int currentX;
30 | int currentY;
31 |
32 | currentX = 0;
33 | currentY = 0;
34 | cell = 0;
35 | cellSize = 38;
36 | rows = 4;
37 | cols = 7;
38 | w = cols * cellSize;
39 | h = rows * cellSize;
40 | processedCells = 11;
41 |
42 | bitmap = new Bitmap(w + 1, h + 1);
43 |
44 | using (Graphics g = Graphics.FromImage(bitmap))
45 | {
46 | g.Clear(Color.Transparent);
47 |
48 | for (int row = 0; row < rows; row++)
49 | {
50 | int cy;
51 |
52 | cy = row * cellSize;
53 |
54 | for (int col = 0; col < cols; col++)
55 | {
56 | int cx;
57 | int cw;
58 | int ch;
59 |
60 | cx = col * cellSize;
61 | cw = cellSize - 1;
62 | ch = cellSize - 1;
63 |
64 | g.DrawLine(Pens.Black, cx, cy, cx + cw, cy);
65 | g.DrawLine(Pens.Black, cx, cy, cx, cy + ch);
66 |
67 | cell++;
68 | if (cell < processedCells)
69 | {
70 | g.FillRectangle(Brushes.Silver, cx + 1, cy + 1, cw, ch);
71 | }
72 | else if (cell == processedCells)
73 | {
74 | currentX = cx;
75 | currentY = cy;
76 | g.FillRectangle(Brushes.CornflowerBlue, cx + 1, cy + 1, cw, ch);
77 | }
78 | else
79 | {
80 | g.FillRectangle(Brushes.DarkSeaGreen, cx + 1, cy + 1, cw, ch);
81 | }
82 |
83 | if (col == cols - 1)
84 | {
85 | g.DrawLine(Pens.Black, cx + cw + 1, cy, cx + cw + 1, cy + ch);
86 | }
87 |
88 | if (row == rows - 1)
89 | {
90 | g.DrawLine(Pens.Black, cx, cy + ch + 1, cx + cw, cy + ch + 1);
91 | }
92 | }
93 | }
94 |
95 | ArticleDiagrams.DrawString(g, "8/32", currentX + cellSize, currentY, cellSize - 1, cellSize, Color.Black, 10, StringAlignment.Center, StringAlignment.Center);
96 | ArticleDiagrams.DrawString(g, "4/32", currentX + (cellSize * 2), currentY, cellSize - 1, cellSize, Color.Black, 10, StringAlignment.Center, StringAlignment.Center);
97 | ArticleDiagrams.DrawString(g, "2/32", currentX - (cellSize * 2), currentY + cellSize, cellSize, cellSize, Color.Black, 10, StringAlignment.Center, StringAlignment.Center);
98 | ArticleDiagrams.DrawString(g, "4/32", currentX - cellSize, currentY + cellSize, cellSize, cellSize, Color.Black, 10, StringAlignment.Center, StringAlignment.Center);
99 | ArticleDiagrams.DrawString(g, "8/32", currentX, currentY + cellSize, cellSize, cellSize, Color.Black, 10, StringAlignment.Center, StringAlignment.Center);
100 | ArticleDiagrams.DrawString(g, "4/32", currentX + cellSize, currentY + cellSize, cellSize, cellSize, Color.Black, 10, StringAlignment.Center, StringAlignment.Center);
101 | ArticleDiagrams.DrawString(g, "2/32", currentX + (cellSize * 2), currentY + cellSize, cellSize, cellSize, Color.Black, 10, StringAlignment.Center, StringAlignment.Center);
102 | }
103 |
104 | return bitmap;
105 | }
106 |
107 | public static Bitmap CreateFloydSteinbergDiagram()
108 | {
109 | Bitmap bitmap;
110 | int w;
111 | int h;
112 | int cellSize;
113 | int halfCellSize;
114 | int rows;
115 | int cols;
116 | int processedCells;
117 | int cell;
118 | int currentX;
119 | int currentY;
120 |
121 | currentX = 0;
122 | currentY = 0;
123 | cell = 0;
124 | cellSize = 38;
125 | halfCellSize = cellSize >> 1;
126 | rows = 4;
127 | cols = 5;
128 | w = cols * cellSize;
129 | h = rows * cellSize;
130 | processedCells = 8;
131 |
132 | bitmap = new Bitmap(w + 1, h + 1);
133 |
134 | using (Graphics g = Graphics.FromImage(bitmap))
135 | {
136 | Color arrowColor;
137 | int arrowSize;
138 | int penSize;
139 |
140 | penSize = 2;
141 | arrowSize = 10;
142 | arrowColor = Color.SlateBlue;
143 |
144 | g.Clear(Color.Transparent);
145 |
146 | for (int row = 0; row < rows; row++)
147 | {
148 | int cy;
149 |
150 | cy = row * cellSize;
151 |
152 | for (int col = 0; col < cols; col++)
153 | {
154 | int cx;
155 | int cw;
156 | int ch;
157 |
158 | cx = col * cellSize;
159 | cw = cellSize - 1;
160 | ch = cellSize - 1;
161 |
162 | g.DrawLine(Pens.Black, cx, cy, cx + cw, cy);
163 | g.DrawLine(Pens.Black, cx, cy, cx, cy + ch);
164 |
165 | cell++;
166 | if (cell < processedCells)
167 | {
168 | g.FillRectangle(Brushes.Silver, cx + 1, cy + 1, cw, ch);
169 | }
170 | else if (cell == processedCells)
171 | {
172 | currentX = cx;
173 | currentY = cy;
174 | g.FillRectangle(Brushes.CornflowerBlue, cx + 1, cy + 1, cw, ch);
175 | }
176 | else
177 | {
178 | g.FillRectangle(Brushes.DarkSeaGreen, cx + 1, cy + 1, cw, ch);
179 | }
180 |
181 | if (col == cols - 1)
182 | {
183 | g.DrawLine(Pens.Black, cx + cw + 1, cy, cx + cw + 1, cy + ch);
184 | }
185 |
186 | if (row == rows - 1)
187 | {
188 | g.DrawLine(Pens.Black, cx, cy + ch + 1, cx + cw, cy + ch + 1);
189 | }
190 | }
191 | }
192 |
193 | ArticleDiagrams.DrawArrow(g, arrowColor, currentX + halfCellSize, currentY + halfCellSize, (int)(cellSize * 0.75), 0, penSize, arrowSize);
194 | ArticleDiagrams.DrawArrow(g, arrowColor, currentX + halfCellSize, currentY + halfCellSize, (int)(cellSize * 1.2), 45, penSize, arrowSize);
195 | ArticleDiagrams.DrawArrow(g, arrowColor, currentX + halfCellSize, currentY + halfCellSize, (int)(cellSize * 0.9), 90, penSize, arrowSize);
196 | ArticleDiagrams.DrawArrow(g, arrowColor, currentX + halfCellSize, currentY + halfCellSize, (int)(cellSize * 1.2), 135, penSize, arrowSize);
197 |
198 | ArticleDiagrams.DrawString(g, "7/16", currentX + cellSize, currentY, cellSize - 1, cellSize, Color.Black, 10, StringAlignment.Far, StringAlignment.Center);
199 | ArticleDiagrams.DrawString(g, "3/16", currentX - cellSize, currentY + cellSize, cellSize, cellSize, Color.Black, 10, StringAlignment.Center, StringAlignment.Far);
200 | ArticleDiagrams.DrawString(g, "5/16", currentX, currentY + cellSize, cellSize, cellSize, Color.Black, 10, StringAlignment.Center, StringAlignment.Far);
201 | ArticleDiagrams.DrawString(g, "1/16", currentX + cellSize, currentY + cellSize, cellSize, cellSize, Color.Black, 10, StringAlignment.Center, StringAlignment.Far);
202 | }
203 |
204 | return bitmap;
205 | }
206 |
207 | public static Bitmap CreateIntroDiagram()
208 | {
209 | Bitmap bitmap;
210 | int w;
211 | int h;
212 | int cellSize;
213 | int halfCellSize;
214 | int rows;
215 | int cols;
216 | int processedCells;
217 | int cell;
218 | int currentX;
219 | int currentY;
220 |
221 | currentX = 0;
222 | currentY = 0;
223 | cell = 0;
224 | cellSize = 32;
225 | halfCellSize = cellSize >> 1;
226 | rows = 5;
227 | cols = 8;
228 | w = cols * cellSize;
229 | h = rows * cellSize;
230 | processedCells = (rows * cols) >> 1;
231 |
232 | bitmap = new Bitmap(w + 1, h + 1);
233 |
234 | using (Graphics g = Graphics.FromImage(bitmap))
235 | {
236 | Color arrowColor;
237 | int arrowSize;
238 | int penSize;
239 |
240 | penSize = 2;
241 | arrowSize = 10;
242 | arrowColor = Color.SlateBlue;
243 |
244 | g.Clear(Color.Transparent);
245 |
246 | for (int row = 0; row < rows; row++)
247 | {
248 | int cy;
249 |
250 | cy = row * cellSize;
251 |
252 | for (int col = 0; col < cols; col++)
253 | {
254 | int cx;
255 | int cw;
256 | int ch;
257 |
258 | cx = col * cellSize;
259 | cw = cellSize - 1;
260 | ch = cellSize - 1;
261 |
262 | g.DrawLine(Pens.Black, cx, cy, cx + cw, cy);
263 | g.DrawLine(Pens.Black, cx, cy, cx, cy + ch);
264 |
265 | cell++;
266 | if (cell < processedCells)
267 | {
268 | g.FillRectangle(Brushes.Silver, cx + 1, cy + 1, cw, ch);
269 | }
270 | else if (cell == processedCells)
271 | {
272 | currentX = cx;
273 | currentY = cy;
274 | g.FillRectangle(Brushes.CornflowerBlue, cx + 1, cy + 1, cw, ch);
275 | }
276 | else
277 | {
278 | g.FillRectangle(Brushes.DarkSeaGreen, cx + 1, cy + 1, cw, ch);
279 | }
280 |
281 | if (col == cols - 1)
282 | {
283 | g.DrawLine(Pens.Black, cx + cw + 1, cy, cx + cw + 1, cy + ch);
284 | }
285 |
286 | if (row == rows - 1)
287 | {
288 | g.DrawLine(Pens.Black, cx, cy + ch + 1, cx + cw, cy + ch + 1);
289 | }
290 | }
291 | }
292 |
293 | ArticleDiagrams.DrawArrow(g, arrowColor, currentX + halfCellSize, currentY + halfCellSize, cellSize, 0, penSize, arrowSize);
294 | ArticleDiagrams.DrawArrow(g, arrowColor, currentX + halfCellSize, currentY + halfCellSize, (int)(cellSize * 1.4), 45, penSize, arrowSize);
295 | ArticleDiagrams.DrawArrow(g, arrowColor, currentX + halfCellSize, currentY + halfCellSize, cellSize, 90, penSize, arrowSize);
296 | ArticleDiagrams.DrawArrow(g, arrowColor, currentX + halfCellSize, currentY + halfCellSize, (int)(cellSize * 1.4), 135, penSize, arrowSize);
297 | }
298 |
299 | return bitmap;
300 | }
301 |
302 | private static void DrawArrow(Graphics g, Color color, int x, int y, int w, int angle, int penSize, int arrowSize)
303 | {
304 | using (Matrix rotation = new Matrix(1, 0, 0, 1, 0, 0))
305 | {
306 | int halfArrowSize;
307 | halfArrowSize = arrowSize >> 1;
308 |
309 | rotation.RotateAt(angle, new Point(x, y));
310 | g.Transform = rotation;
311 |
312 | g.SmoothingMode = SmoothingMode.AntiAlias;
313 |
314 | using (Pen pen = new Pen(color, penSize))
315 | {
316 | g.DrawLine(pen, x, y, x + w, y);
317 | }
318 |
319 | using (Brush brush = new SolidBrush(color))
320 | {
321 | g.FillPolygon(brush, new[]
322 | {
323 | new Point(x + w + penSize - arrowSize, y - halfArrowSize), new Point(x + w + penSize, y), new Point(x + w + penSize - arrowSize, y + halfArrowSize)
324 | });
325 | }
326 |
327 | g.SmoothingMode = SmoothingMode.Default;
328 |
329 | g.ResetTransform();
330 | }
331 | }
332 |
333 | private static void DrawString(Graphics g, string text, int x, int y, int w, int h, Color color, int size, StringAlignment align, StringAlignment verticalAlign)
334 | {
335 | using (Font font = new Font("Segoe UI", size, FontStyle.Regular, GraphicsUnit.Point))
336 | {
337 | using (Brush brush = new SolidBrush(color))
338 | {
339 | using (StringFormat format = (StringFormat)StringFormat.GenericTypographic.Clone())
340 | {
341 | format.Alignment = align;
342 | format.LineAlignment = verticalAlign;
343 | format.HotkeyPrefix = HotkeyPrefix.None;
344 |
345 | g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
346 | g.DrawString(text, font, brush, new Rectangle(x, y, w, h), format);
347 | g.TextRenderingHint = TextRenderingHint.SystemDefault;
348 | }
349 | }
350 | }
351 | }
352 |
353 | #endregion
354 | }
355 | }
356 |
--------------------------------------------------------------------------------
/src/Transforms/SimpleIndexedPalettePixelTransform256.cs:
--------------------------------------------------------------------------------
1 | using Cyotek.Drawing;
2 |
3 | /* Finding nearest colors using Euclidean distance
4 | * https://www.cyotek.com/blog/finding-nearest-colors-using-euclidean-distance
5 | *
6 | * Copyright © 2017 Cyotek Ltd.
7 | */
8 |
9 | namespace Cyotek.DitheringTest.Transforms
10 | {
11 | internal sealed class SimpleIndexedPalettePixelTransform256 : SimpleIndexedPalettePixelTransform
12 | {
13 | #region Constructors
14 |
15 | public SimpleIndexedPalettePixelTransform256()
16 | : base(new[]
17 | {
18 | ArgbColor.FromArgb(0, 0, 0),
19 | ArgbColor.FromArgb(128, 0, 0),
20 | ArgbColor.FromArgb(0, 128, 0),
21 | ArgbColor.FromArgb(128, 128, 0),
22 | ArgbColor.FromArgb(0, 0, 128),
23 | ArgbColor.FromArgb(128, 0, 128),
24 | ArgbColor.FromArgb(0, 128, 128),
25 | ArgbColor.FromArgb(192, 192, 192),
26 | ArgbColor.FromArgb(128, 128, 128),
27 | ArgbColor.FromArgb(255, 0, 0),
28 | ArgbColor.FromArgb(0, 255, 0),
29 | ArgbColor.FromArgb(255, 255, 0),
30 | ArgbColor.FromArgb(0, 0, 255),
31 | ArgbColor.FromArgb(255, 0, 255),
32 | ArgbColor.FromArgb(0, 255, 255),
33 | ArgbColor.FromArgb(255, 255, 255),
34 | ArgbColor.FromArgb(25, 25, 25),
35 | ArgbColor.FromArgb(51, 51, 51),
36 | ArgbColor.FromArgb(76, 76, 76),
37 | ArgbColor.FromArgb(90, 90, 90),
38 | ArgbColor.FromArgb(102, 102, 102),
39 | ArgbColor.FromArgb(115, 115, 115),
40 | ArgbColor.FromArgb(128, 128, 128),
41 | ArgbColor.FromArgb(141, 141, 141),
42 | ArgbColor.FromArgb(153, 153, 153),
43 | ArgbColor.FromArgb(166, 166, 166),
44 | ArgbColor.FromArgb(178, 178, 178),
45 | ArgbColor.FromArgb(192, 192, 192),
46 | ArgbColor.FromArgb(204, 204, 204),
47 | ArgbColor.FromArgb(218, 218, 218),
48 | ArgbColor.FromArgb(230, 230, 230),
49 | ArgbColor.FromArgb(243, 243, 243),
50 | ArgbColor.FromArgb(63, 0, 0),
51 | ArgbColor.FromArgb(92, 0, 0),
52 | ArgbColor.FromArgb(120, 0, 0),
53 | ArgbColor.FromArgb(148, 0, 0),
54 | ArgbColor.FromArgb(177, 0, 0),
55 | ArgbColor.FromArgb(205, 0, 0),
56 | ArgbColor.FromArgb(233, 0, 0),
57 | ArgbColor.FromArgb(254, 7, 7),
58 | ArgbColor.FromArgb(255, 35, 35),
59 | ArgbColor.FromArgb(255, 63, 63),
60 | ArgbColor.FromArgb(255, 92, 92),
61 | ArgbColor.FromArgb(255, 120, 120),
62 | ArgbColor.FromArgb(254, 148, 148),
63 | ArgbColor.FromArgb(255, 177, 177),
64 | ArgbColor.FromArgb(254, 205, 205),
65 | ArgbColor.FromArgb(255, 233, 233),
66 | ArgbColor.FromArgb(63, 23, 0),
67 | ArgbColor.FromArgb(92, 34, 0),
68 | ArgbColor.FromArgb(120, 45, 0),
69 | ArgbColor.FromArgb(148, 55, 0),
70 | ArgbColor.FromArgb(177, 66, 0),
71 | ArgbColor.FromArgb(205, 77, 0),
72 | ArgbColor.FromArgb(233, 87, 0),
73 | ArgbColor.FromArgb(254, 100, 7),
74 | ArgbColor.FromArgb(255, 117, 35),
75 | ArgbColor.FromArgb(255, 135, 63),
76 | ArgbColor.FromArgb(255, 153, 92),
77 | ArgbColor.FromArgb(255, 170, 120),
78 | ArgbColor.FromArgb(254, 188, 148),
79 | ArgbColor.FromArgb(255, 206, 177),
80 | ArgbColor.FromArgb(254, 224, 205),
81 | ArgbColor.FromArgb(255, 241, 233),
82 | ArgbColor.FromArgb(63, 47, 0),
83 | ArgbColor.FromArgb(92, 69, 0),
84 | ArgbColor.FromArgb(120, 90, 0),
85 | ArgbColor.FromArgb(148, 111, 0),
86 | ArgbColor.FromArgb(177, 132, 0),
87 | ArgbColor.FromArgb(205, 154, 0),
88 | ArgbColor.FromArgb(233, 175, 0),
89 | ArgbColor.FromArgb(254, 193, 7),
90 | ArgbColor.FromArgb(255, 200, 35),
91 | ArgbColor.FromArgb(255, 207, 63),
92 | ArgbColor.FromArgb(255, 214, 92),
93 | ArgbColor.FromArgb(255, 221, 120),
94 | ArgbColor.FromArgb(254, 228, 148),
95 | ArgbColor.FromArgb(255, 235, 177),
96 | ArgbColor.FromArgb(254, 242, 205),
97 | ArgbColor.FromArgb(255, 249, 233),
98 | ArgbColor.FromArgb(55, 63, 0),
99 | ArgbColor.FromArgb(80, 92, 0),
100 | ArgbColor.FromArgb(105, 120, 0),
101 | ArgbColor.FromArgb(130, 148, 0),
102 | ArgbColor.FromArgb(154, 177, 0),
103 | ArgbColor.FromArgb(179, 205, 0),
104 | ArgbColor.FromArgb(204, 233, 0),
105 | ArgbColor.FromArgb(224, 254, 7),
106 | ArgbColor.FromArgb(227, 255, 35),
107 | ArgbColor.FromArgb(231, 255, 63),
108 | ArgbColor.FromArgb(234, 255, 92),
109 | ArgbColor.FromArgb(238, 255, 120),
110 | ArgbColor.FromArgb(241, 254, 148),
111 | ArgbColor.FromArgb(245, 255, 177),
112 | ArgbColor.FromArgb(248, 254, 205),
113 | ArgbColor.FromArgb(252, 255, 233),
114 | ArgbColor.FromArgb(31, 63, 0),
115 | ArgbColor.FromArgb(46, 92, 0),
116 | ArgbColor.FromArgb(60, 120, 0),
117 | ArgbColor.FromArgb(74, 148, 0),
118 | ArgbColor.FromArgb(88, 177, 0),
119 | ArgbColor.FromArgb(102, 205, 0),
120 | ArgbColor.FromArgb(116, 233, 0),
121 | ArgbColor.FromArgb(131, 254, 7),
122 | ArgbColor.FromArgb(145, 255, 35),
123 | ArgbColor.FromArgb(159, 255, 63),
124 | ArgbColor.FromArgb(173, 255, 92),
125 | ArgbColor.FromArgb(187, 255, 120),
126 | ArgbColor.FromArgb(201, 254, 148),
127 | ArgbColor.FromArgb(216, 255, 177),
128 | ArgbColor.FromArgb(230, 254, 205),
129 | ArgbColor.FromArgb(244, 255, 233),
130 | ArgbColor.FromArgb(7, 63, 0),
131 | ArgbColor.FromArgb(11, 92, 0),
132 | ArgbColor.FromArgb(15, 120, 0),
133 | ArgbColor.FromArgb(18, 148, 0),
134 | ArgbColor.FromArgb(22, 177, 0),
135 | ArgbColor.FromArgb(25, 205, 0),
136 | ArgbColor.FromArgb(29, 233, 0),
137 | ArgbColor.FromArgb(38, 254, 7),
138 | ArgbColor.FromArgb(62, 255, 35),
139 | ArgbColor.FromArgb(87, 255, 63),
140 | ArgbColor.FromArgb(112, 255, 92),
141 | ArgbColor.FromArgb(137, 255, 120),
142 | ArgbColor.FromArgb(162, 254, 148),
143 | ArgbColor.FromArgb(186, 255, 177),
144 | ArgbColor.FromArgb(211, 254, 205),
145 | ArgbColor.FromArgb(236, 255, 233),
146 | ArgbColor.FromArgb(0, 63, 15),
147 | ArgbColor.FromArgb(0, 92, 23),
148 | ArgbColor.FromArgb(0, 120, 30),
149 | ArgbColor.FromArgb(0, 148, 37),
150 | ArgbColor.FromArgb(0, 177, 44),
151 | ArgbColor.FromArgb(0, 205, 51),
152 | ArgbColor.FromArgb(0, 233, 58),
153 | ArgbColor.FromArgb(7, 254, 69),
154 | ArgbColor.FromArgb(35, 255, 90),
155 | ArgbColor.FromArgb(63, 255, 111),
156 | ArgbColor.FromArgb(92, 255, 132),
157 | ArgbColor.FromArgb(120, 255, 154),
158 | ArgbColor.FromArgb(148, 254, 175),
159 | ArgbColor.FromArgb(177, 255, 196),
160 | ArgbColor.FromArgb(205, 254, 217),
161 | ArgbColor.FromArgb(233, 255, 239),
162 | ArgbColor.FromArgb(0, 63, 39),
163 | ArgbColor.FromArgb(0, 92, 57),
164 | ArgbColor.FromArgb(0, 120, 75),
165 | ArgbColor.FromArgb(0, 148, 92),
166 | ArgbColor.FromArgb(0, 177, 110),
167 | ArgbColor.FromArgb(0, 205, 128),
168 | ArgbColor.FromArgb(0, 233, 146),
169 | ArgbColor.FromArgb(7, 254, 162),
170 | ArgbColor.FromArgb(35, 255, 172),
171 | ArgbColor.FromArgb(63, 255, 183),
172 | ArgbColor.FromArgb(92, 255, 193),
173 | ArgbColor.FromArgb(120, 255, 204),
174 | ArgbColor.FromArgb(148, 254, 215),
175 | ArgbColor.FromArgb(177, 255, 225),
176 | ArgbColor.FromArgb(205, 254, 236),
177 | ArgbColor.FromArgb(233, 255, 247),
178 | ArgbColor.FromArgb(0, 63, 63),
179 | ArgbColor.FromArgb(0, 92, 92),
180 | ArgbColor.FromArgb(0, 120, 120),
181 | ArgbColor.FromArgb(0, 148, 148),
182 | ArgbColor.FromArgb(0, 177, 177),
183 | ArgbColor.FromArgb(0, 205, 205),
184 | ArgbColor.FromArgb(0, 233, 233),
185 | ArgbColor.FromArgb(7, 254, 254),
186 | ArgbColor.FromArgb(35, 255, 255),
187 | ArgbColor.FromArgb(63, 255, 255),
188 | ArgbColor.FromArgb(92, 255, 255),
189 | ArgbColor.FromArgb(120, 255, 255),
190 | ArgbColor.FromArgb(148, 254, 254),
191 | ArgbColor.FromArgb(177, 255, 255),
192 | ArgbColor.FromArgb(205, 254, 254),
193 | ArgbColor.FromArgb(233, 255, 255),
194 | ArgbColor.FromArgb(0, 39, 63),
195 | ArgbColor.FromArgb(0, 57, 92),
196 | ArgbColor.FromArgb(0, 75, 120),
197 | ArgbColor.FromArgb(0, 92, 148),
198 | ArgbColor.FromArgb(0, 110, 177),
199 | ArgbColor.FromArgb(0, 128, 205),
200 | ArgbColor.FromArgb(0, 146, 233),
201 | ArgbColor.FromArgb(7, 162, 254),
202 | ArgbColor.FromArgb(35, 172, 255),
203 | ArgbColor.FromArgb(63, 183, 255),
204 | ArgbColor.FromArgb(92, 193, 255),
205 | ArgbColor.FromArgb(120, 204, 255),
206 | ArgbColor.FromArgb(148, 215, 254),
207 | ArgbColor.FromArgb(177, 225, 255),
208 | ArgbColor.FromArgb(205, 236, 254),
209 | ArgbColor.FromArgb(233, 247, 255),
210 | ArgbColor.FromArgb(0, 15, 63),
211 | ArgbColor.FromArgb(0, 23, 92),
212 | ArgbColor.FromArgb(0, 30, 120),
213 | ArgbColor.FromArgb(0, 37, 148),
214 | ArgbColor.FromArgb(0, 44, 177),
215 | ArgbColor.FromArgb(0, 51, 205),
216 | ArgbColor.FromArgb(0, 58, 233),
217 | ArgbColor.FromArgb(7, 69, 254),
218 | ArgbColor.FromArgb(35, 90, 255),
219 | ArgbColor.FromArgb(63, 111, 255),
220 | ArgbColor.FromArgb(92, 132, 255),
221 | ArgbColor.FromArgb(120, 154, 255),
222 | ArgbColor.FromArgb(148, 175, 254),
223 | ArgbColor.FromArgb(177, 196, 255),
224 | ArgbColor.FromArgb(205, 217, 254),
225 | ArgbColor.FromArgb(233, 239, 255),
226 | ArgbColor.FromArgb(7, 0, 63),
227 | ArgbColor.FromArgb(11, 0, 92),
228 | ArgbColor.FromArgb(15, 0, 120),
229 | ArgbColor.FromArgb(18, 0, 148),
230 | ArgbColor.FromArgb(22, 0, 177),
231 | ArgbColor.FromArgb(25, 0, 205),
232 | ArgbColor.FromArgb(29, 0, 233),
233 | ArgbColor.FromArgb(38, 7, 254),
234 | ArgbColor.FromArgb(62, 35, 255),
235 | ArgbColor.FromArgb(87, 63, 255),
236 | ArgbColor.FromArgb(112, 92, 255),
237 | ArgbColor.FromArgb(137, 120, 255),
238 | ArgbColor.FromArgb(162, 148, 254),
239 | ArgbColor.FromArgb(186, 177, 255),
240 | ArgbColor.FromArgb(211, 205, 254),
241 | ArgbColor.FromArgb(236, 233, 255),
242 | ArgbColor.FromArgb(31, 0, 63),
243 | ArgbColor.FromArgb(46, 0, 92),
244 | ArgbColor.FromArgb(60, 0, 120),
245 | ArgbColor.FromArgb(74, 0, 148),
246 | ArgbColor.FromArgb(88, 0, 177),
247 | ArgbColor.FromArgb(102, 0, 205),
248 | ArgbColor.FromArgb(116, 0, 233),
249 | ArgbColor.FromArgb(131, 7, 254),
250 | ArgbColor.FromArgb(145, 35, 255),
251 | ArgbColor.FromArgb(159, 63, 255),
252 | ArgbColor.FromArgb(173, 92, 255),
253 | ArgbColor.FromArgb(187, 120, 255),
254 | ArgbColor.FromArgb(201, 148, 254),
255 | ArgbColor.FromArgb(216, 177, 255),
256 | ArgbColor.FromArgb(230, 205, 254),
257 | ArgbColor.FromArgb(244, 233, 255),
258 | ArgbColor.FromArgb(55, 0, 63),
259 | ArgbColor.FromArgb(80, 0, 92),
260 | ArgbColor.FromArgb(105, 0, 120),
261 | ArgbColor.FromArgb(130, 0, 148),
262 | ArgbColor.FromArgb(154, 0, 177),
263 | ArgbColor.FromArgb(179, 0, 205),
264 | ArgbColor.FromArgb(204, 0, 233),
265 | ArgbColor.FromArgb(224, 7, 254),
266 | ArgbColor.FromArgb(227, 35, 255),
267 | ArgbColor.FromArgb(231, 63, 255),
268 | ArgbColor.FromArgb(234, 92, 255),
269 | ArgbColor.FromArgb(238, 120, 255),
270 | ArgbColor.FromArgb(241, 148, 254),
271 | ArgbColor.FromArgb(245, 177, 255),
272 | ArgbColor.FromArgb(248, 205, 254),
273 | ArgbColor.FromArgb(252, 233, 255)
274 | })
275 | { }
276 |
277 | #endregion
278 | }
279 | }
280 |
--------------------------------------------------------------------------------
/src/MainForm.cs:
--------------------------------------------------------------------------------
1 | #define USEWORKER
2 | //#undef USEWORKER
3 | using System;
4 | using System.Collections.Generic;
5 | using System.ComponentModel;
6 | using System.Drawing;
7 | using System.Drawing.Imaging;
8 | using System.IO;
9 | using System.Media;
10 | using System.Threading;
11 | using System.Windows.Forms;
12 | using Cyotek.DitheringTest.Helpers;
13 | using Cyotek.DitheringTest.Transforms;
14 | using Cyotek.Drawing;
15 | using Cyotek.Drawing.Imaging.ColorReduction;
16 | using Cyotek.Windows.Forms;
17 |
18 | /* Dithering an image using the Floyd–Steinberg algorithm in C#
19 | * https://www.cyotek.com/blog/dithering-an-image-using-the-floyd-steinberg-algorithm-in-csharp
20 | *
21 | * Copyright © 2015-2017 Cyotek Ltd.
22 | *
23 | * Licensed under the MIT License. See LICENSE.txt for the full text.
24 | */
25 |
26 | namespace Cyotek.DitheringTest
27 | {
28 | internal partial class MainForm : Form
29 | {
30 | #region Fields
31 |
32 | private Bitmap _image;
33 |
34 | private ArgbColor[] _originalImage;
35 |
36 | private RadioButton _previousDitherSelection;
37 |
38 | private RadioButton _previousTransformSelection;
39 |
40 | private Bitmap _transformed;
41 |
42 | private ArgbColor[] _transformedImage;
43 |
44 | #endregion
45 |
46 | #region Constructors
47 |
48 | public MainForm()
49 | {
50 | this.InitializeComponent();
51 | }
52 |
53 | #endregion
54 |
55 | #region Methods
56 |
57 | ///
58 | /// Clean up any resources being used.
59 | ///
60 | /// true if managed resources should be disposed; otherwise, false.
61 | protected override void Dispose(bool disposing)
62 | {
63 | if (disposing)
64 | {
65 | this.CleanUpOriginal();
66 | this.CleanUpTransformed();
67 |
68 | if (components != null)
69 | {
70 | components.Dispose();
71 | }
72 | }
73 | base.Dispose(disposing);
74 | }
75 |
76 | ///
77 | /// Raises the event.
78 | ///
79 | /// A that contains the event data.
80 | protected override void OnShown(EventArgs e)
81 | {
82 | base.OnShown(e);
83 |
84 | paletteSizeComboBox.SelectedIndex = 0;
85 | noDitherRadioButton.Checked = true;
86 |
87 | this.OpenImage(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"resources\sample.png"));
88 |
89 | //this.OpenImage(ArticleDiagrams.CreateBurkesDiagram());
90 | //ArticleDiagrams.CreateBurkesDiagram().Save(@"C:\Checkout\cyotek\source\Applications\cyotek.com\files\articleimages\dithering-burkes-diagram.png", ImageFormat.Png);
91 | }
92 |
93 | private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
94 | {
95 | using (AboutDialog dialog = new AboutDialog())
96 | {
97 | dialog.ShowDialog(this);
98 | }
99 | }
100 |
101 | private void actualSizeToolStripButton_Click(object sender, EventArgs e)
102 | {
103 | originalImageBox.ActualSize();
104 | }
105 |
106 | private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
107 | {
108 | WorkerData data;
109 |
110 | data = (WorkerData)e.Argument;
111 |
112 | e.Result = this.GetTransformedImage(data);
113 | }
114 |
115 | private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
116 | {
117 | this.CleanUpTransformed();
118 |
119 | if (e.Error != null)
120 | {
121 | MessageBox.Show("Failed to transform image. " + e.Error.GetBaseException().Message, this.Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
122 | }
123 | else
124 | {
125 | _transformed = e.Result as Bitmap;
126 | _transformedImage = _transformed.GetPixelsFrom32BitArgbImage();
127 |
128 | transformedImageBox.Image = _transformed;
129 |
130 | ThreadPool.QueueUserWorkItem(state =>
131 | {
132 | int count;
133 |
134 | count = this.GetColorCount(_transformedImage);
135 |
136 | this.UpdateColorCount(transformedColorsToolStripStatusLabel, count);
137 | });
138 | }
139 |
140 | statusToolStripStatusLabel.Text = string.Empty;
141 | Cursor.Current = Cursors.Default;
142 | this.UseWaitCursor = false;
143 | }
144 |
145 | private void CleanUpOriginal()
146 | {
147 | originalImageBox.Image = null;
148 |
149 | if (_image != null)
150 | {
151 | _image.Dispose();
152 | _image = null;
153 | }
154 | }
155 |
156 | ///
157 | /// Disposes of interim images
158 | ///
159 | private void CleanUpTransformed()
160 | {
161 | transformedImageBox.Image = null;
162 | transformedColorsToolStripStatusLabel.Text = string.Empty;
163 |
164 | if (_transformed != null)
165 | {
166 | _transformed.Dispose();
167 | _transformed = null;
168 | }
169 | }
170 |
171 | private void copyMergedToolStripMenuItem_Click(object sender, EventArgs e)
172 | {
173 | if (_image != null && _transformed != null)
174 | {
175 | int iw;
176 | int ih;
177 | int x2;
178 | int y2;
179 | int fw;
180 | int fh;
181 |
182 | fw = _image.Width;
183 | fh = _image.Height;
184 |
185 | if (horizontalSplitToolStripMenuItem.Checked)
186 | {
187 | iw = fw;
188 | ih = fh * 2;
189 | x2 = 0;
190 | y2 = fh;
191 | }
192 | else
193 | {
194 | iw = fw * 2;
195 | ih = fh;
196 | x2 = fw;
197 | y2 = 0;
198 | }
199 |
200 | using (Bitmap image = new Bitmap(iw, ih, PixelFormat.Format32bppArgb))
201 | {
202 | using (Graphics g = Graphics.FromImage(image))
203 | {
204 | g.DrawImage(_image, new Rectangle(0, 0, fw, fh), new Rectangle(0, 0, fw, fh), GraphicsUnit.Pixel);
205 | g.DrawImage(_transformed, new Rectangle(x2, y2, fw, fh), new Rectangle(0, 0, fw, fh), GraphicsUnit.Pixel);
206 | }
207 |
208 | ClipboardHelpers.CopyImage(image);
209 | }
210 | }
211 | else
212 | {
213 | MessageBox.Show("Both the source and transformed images must exist to make a merged copy.", "Copy Image", MessageBoxButtons.OK, MessageBoxIcon.Information);
214 | }
215 | }
216 |
217 | private void copySourceToolStripMenuItem_Click(object sender, EventArgs e)
218 | {
219 | ClipboardHelpers.CopyImage(_image);
220 | }
221 |
222 | private void copyTransformedToolStripMenuItem_Click(object sender, EventArgs e)
223 | {
224 | ClipboardHelpers.CopyImage(_transformed);
225 | }
226 |
227 | private void DefineImageBoxes(object sender, out ImageBox source, out ImageBox dest)
228 | {
229 | source = (ImageBox)sender;
230 | dest = source == originalImageBox ? transformedImageBox : originalImageBox;
231 | }
232 |
233 | private void DitherCheckBoxCheckedChangedHandler(object sender, EventArgs e)
234 | {
235 | this.UpdateRadioSelection(sender as RadioButton, ref _previousDitherSelection);
236 |
237 | refreshButton.Enabled = randomRadioButton.Checked;
238 |
239 | this.RequestImageTransform();
240 | }
241 |
242 | private void exitToolStripMenuItem_Click(object sender, EventArgs e)
243 | {
244 | this.Close();
245 | }
246 |
247 | private int GetColorCount(ArgbColor[] pixels)
248 | {
249 | HashSet colors;
250 |
251 | colors = new HashSet();
252 |
253 | foreach (ArgbColor color in pixels)
254 | {
255 | colors.Add(color.ToArgb());
256 | }
257 |
258 | return colors.Count;
259 | }
260 |
261 | private IErrorDiffusion GetDitheringInstance()
262 | {
263 | IErrorDiffusion result;
264 |
265 | if (floydSteinbergRadioButton.Checked)
266 | {
267 | result = new FloydSteinbergDithering();
268 | }
269 | else if (burkesRadioButton.Checked)
270 | {
271 | result = new BurksDithering();
272 | }
273 | else if (jarvisJudiceNinkeDitheringradioButton.Checked)
274 | {
275 | result = new JarvisJudiceNinkeDithering();
276 | }
277 | else if (stuckiRadioButton.Checked)
278 | {
279 | result = new StuckiDithering();
280 | }
281 | else if (sierra3RadioButton.Checked)
282 | {
283 | result = new Sierra3Dithering();
284 | }
285 | else if (sierra2RadioButton.Checked)
286 | {
287 | result = new Sierra2Dithering();
288 | }
289 | else if (sierraLiteRadioButton.Checked)
290 | {
291 | result = new SierraLiteDithering();
292 | }
293 | else if (atkinsonRadioButton.Checked)
294 | {
295 | result = new AtkinsonDithering();
296 | }
297 | else if (randomRadioButton.Checked)
298 | {
299 | result = new RandomDithering();
300 | }
301 | else if (bayer2RadioButton.Checked)
302 | {
303 | result = new Bayer2();
304 | }
305 | else if (bayer3RadioButton.Checked)
306 | {
307 | result = new Bayer3();
308 | }
309 | else if (bayer4RadioButton.Checked)
310 | {
311 | result = new Bayer4();
312 | }
313 | else if (bayer8RadioButton.Checked)
314 | {
315 | result = new Bayer8();
316 | }
317 | else
318 | {
319 | result = null;
320 | }
321 |
322 | return result;
323 | }
324 |
325 | private int GetMaximumColorCount()
326 | {
327 | int result;
328 |
329 | result = 256;
330 |
331 | if (monochromeRadioButton.Checked)
332 | {
333 | result = 2;
334 | }
335 | else if (colorRadioButton.Checked)
336 | {
337 | switch (paletteSizeComboBox.SelectedIndex)
338 | {
339 | case 0:
340 | result = 8;
341 | break;
342 | case 1:
343 | result = 16;
344 | break;
345 | case 2:
346 | result = 256;
347 | break;
348 | }
349 | }
350 |
351 | return result;
352 | }
353 |
354 | private IPixelTransform GetPixelTransform()
355 | {
356 | IPixelTransform result;
357 |
358 | result = null;
359 |
360 | if (monochromeRadioButton.Checked)
361 | {
362 | result = new MonochromePixelTransform((byte)thresholdNumericUpDown.Value);
363 | }
364 | else if (colorRadioButton.Checked)
365 | {
366 | switch (paletteSizeComboBox.SelectedIndex)
367 | {
368 | case 0:
369 | result = new SimpleIndexedPalettePixelTransform8();
370 | break;
371 | case 1:
372 | result = new SimpleIndexedPalettePixelTransform16();
373 | break;
374 | case 2:
375 | result = new SimpleIndexedPalettePixelTransform256();
376 | break;
377 | }
378 | }
379 |
380 | return result;
381 | }
382 |
383 | private Bitmap GetTransformedImage(WorkerData workerData)
384 | {
385 | Bitmap image;
386 | Bitmap result;
387 | ArgbColor[] pixelData;
388 | Size size;
389 | IPixelTransform transform;
390 | IErrorDiffusion dither;
391 |
392 | transform = workerData.Transform;
393 | dither = workerData.Dither;
394 | image = workerData.Image;
395 | size = image.Size;
396 | pixelData = image.GetPixelsFrom32BitArgbImage();
397 |
398 | if (dither != null && dither.Prescan)
399 | {
400 | // perform the dithering on the source data before
401 | // it is transformed
402 | this.ProcessPixels(pixelData, size, null, dither);
403 | dither = null;
404 | }
405 |
406 | // scan each pixel, apply a transform the pixel
407 | // and then dither it
408 | this.ProcessPixels(pixelData, size, transform, dither);
409 |
410 | // create the final bitmap
411 | result = pixelData.ToBitmap(size);
412 |
413 | return result;
414 | }
415 |
416 | private void horizontalSplitToolStripMenuItem_Click(object sender, EventArgs e)
417 | {
418 | bool horizontal;
419 |
420 | horizontal = !horizontalSplitToolStripMenuItem.Checked;
421 | horizontalSplitToolStripMenuItem.Checked = horizontal;
422 | horizontalToolStripButton.Checked = horizontal;
423 |
424 | if (horizontal)
425 | {
426 | previewSplitContainer.Orientation = Orientation.Horizontal;
427 | previewSplitContainer.SplitterDistance = (previewSplitContainer.Height - previewSplitContainer.SplitterWidth) / 2;
428 | }
429 | else
430 | {
431 | previewSplitContainer.Orientation = Orientation.Vertical;
432 | previewSplitContainer.SplitterDistance = (previewSplitContainer.Width - previewSplitContainer.SplitterWidth) / 2;
433 | }
434 | }
435 |
436 | private void monochromeRadioButton_CheckedChanged(object sender, EventArgs e)
437 | {
438 | this.UpdateRadioSelection(sender as RadioButton, ref _previousTransformSelection);
439 |
440 | monochromePanel.Enabled = monochromeRadioButton.Checked;
441 | colorPanel.Enabled = colorRadioButton.Checked;
442 |
443 | this.RequestImageTransform();
444 | }
445 |
446 | private void OpenImage(Bitmap bitmap)
447 | {
448 | this.CleanUpOriginal();
449 |
450 | // Create a copy of the source bitmap.
451 | // Two reasons:
452 | // 1. The copy routine will ensure a 32bit ARGB image is returned as the unsafe code
453 | // for working with bitmaps via points expects this and I'm not complicating the
454 | // project further by adding tons of code
455 | // 2. Image.FromFile locks the source file until the bitmap is disposed of. I don't
456 | // want that to happen, and copying the image gets rid of this issue too
457 |
458 | _image = bitmap.Copy();
459 | _originalImage = _image.GetPixelsFrom32BitArgbImage();
460 |
461 | originalImageBox.Image = _image;
462 | originalImageBox.ActualSize();
463 |
464 | ThreadPool.QueueUserWorkItem(state =>
465 | {
466 | int count;
467 |
468 | count = this.GetColorCount(_originalImage);
469 |
470 | this.UpdateColorCount(originalColorsToolStripStatusLabel, count);
471 | });
472 |
473 | this.RequestImageTransform();
474 | }
475 |
476 | private void OpenImage(string fileName)
477 | {
478 | try
479 | {
480 | using (Bitmap image = (Bitmap)Image.FromFile(fileName))
481 | {
482 | this.OpenImage(image);
483 | }
484 | }
485 | catch (Exception ex)
486 | {
487 | MessageBox.Show(string.Format("Failed to open image. {0}", ex.GetBaseException().Message), "Open Image", MessageBoxButtons.OK, MessageBoxIcon.Error);
488 | }
489 | }
490 |
491 | private void openToolStripMenuItem_Click(object sender, EventArgs e)
492 | {
493 | using (FileDialog dialog = new OpenFileDialog
494 | {
495 | Title = "Open Image",
496 | DefaultExt = "png",
497 | Filter = "All Pictures (*.emf;*.wmf;*.jpg;*.jpeg;*.jfif;*.jpe;*.png;*.bmp;*.dib;*.rle;*.gif;*.tif;*.tiff)|*.emf;*.wmf;*.jpg;*.jpeg;*.jfif;*.jpe;*.png;*.bmp;*.dib;*.rle;*.gif;*.tif;*.tiff|Windows Enhanced Metafile (*.emf)|*.emf|Windows Metafile (*.wmf)|*.wmf|JPEG File Interchange Format (*.jpg;*.jpeg;*.jfif;*.jpe)|*.jpg;*.jpeg;*.jfif;*.jpe|Portable Networks Graphic (*.png)|*.png|Windows Bitmap (*.bmp;*.dib;*.rle)|*.bmp;*.dib;*.rle|Graphics Interchange Format (*.gif)|*.gif|Tagged Image File Format (*.tif;*.tiff)|*.tif;*.tiff|All files (*.*)|*.*"
498 | })
499 | {
500 | if (dialog.ShowDialog(this) == DialogResult.OK)
501 | {
502 | this.OpenImage(dialog.FileName);
503 | }
504 | }
505 | }
506 |
507 | private void originalImageBox_Scroll(object sender, ScrollEventArgs e)
508 | {
509 | ImageBox source;
510 | ImageBox dest;
511 | Point sourcePosition;
512 | Point destinationPosition;
513 | double aspectW;
514 | double aspectH;
515 |
516 | this.DefineImageBoxes(sender, out source, out dest);
517 |
518 | aspectW = source.Image.Size.Width / (double)dest.Image.Size.Width;
519 | aspectH = source.Image.Size.Height / (double)dest.Image.Size.Height;
520 |
521 | sourcePosition = source.AutoScrollPosition;
522 | destinationPosition = new Point(-(int)(sourcePosition.X * aspectW), -(int)(sourcePosition.Y * aspectH));
523 |
524 | dest.ScrollTo(destinationPosition.X, destinationPosition.Y);
525 | }
526 |
527 | private void originalImageBox_Zoomed(object sender, ImageBoxZoomEventArgs e)
528 | {
529 | ImageBox source;
530 | ImageBox dest;
531 |
532 | this.DefineImageBoxes(sender, out source, out dest);
533 |
534 | dest.Zoom = source.Zoom;
535 |
536 | zoomToolStripStatusLabel.Text = source.Zoom + "%";
537 | }
538 |
539 | private void pasteToolStripMenuItem_Click(object sender, EventArgs e)
540 | {
541 | Bitmap image;
542 |
543 | image = ClipboardHelpers.GetImage();
544 |
545 | if (image == null)
546 | {
547 | SystemSounds.Exclamation.Play();
548 | }
549 | else
550 | {
551 | this.OpenImage(image);
552 | }
553 | }
554 |
555 | private void ProcessPixels(ArgbColor[] pixelData, Size size, IPixelTransform pixelTransform, IErrorDiffusion dither)
556 | {
557 | for (int row = 0; row < size.Height; row++)
558 | {
559 | for (int col = 0; col < size.Width; col++)
560 | {
561 | int index;
562 | ArgbColor current;
563 | ArgbColor transformed;
564 |
565 | index = row * size.Width + col;
566 |
567 | current = pixelData[index];
568 |
569 | // transform the pixel
570 | if (pixelTransform != null)
571 | {
572 | transformed = pixelTransform.Transform(pixelData, current, col, row, size.Width, size.Height);
573 | pixelData[index] = transformed;
574 | }
575 | else
576 | {
577 | transformed = current;
578 | }
579 |
580 | // apply a dither algorithm to this pixel
581 | // assuming it wasn't done before
582 | dither?.Diffuse(pixelData, current, transformed, col, row, size.Width, size.Height);
583 | }
584 | }
585 | }
586 |
587 | private void RequestImageTransform()
588 | {
589 | if (_image != null && !backgroundWorker.IsBusy)
590 | {
591 | WorkerData workerData;
592 | IPixelTransform transform;
593 | IErrorDiffusion ditherer;
594 | Bitmap image;
595 |
596 | statusToolStripStatusLabel.Text = "Running image transform...";
597 | Cursor.Current = Cursors.WaitCursor;
598 | this.UseWaitCursor = true;
599 |
600 | transform = this.GetPixelTransform();
601 | ditherer = this.GetDitheringInstance();
602 | image = _image.Copy();
603 |
604 | workerData = new WorkerData
605 | {
606 | Image = image,
607 | Transform = transform,
608 | Dither = ditherer,
609 | ColorCount = this.GetMaximumColorCount()
610 | };
611 |
612 | #if USEWORKER
613 | backgroundWorker.RunWorkerAsync(workerData);
614 | #else
615 | backgroundWorker_RunWorkerCompleted(backgroundWorker, new RunWorkerCompletedEventArgs(this.GetTransformedImage(workerData), null, false));
616 | #endif
617 | }
618 | }
619 |
620 | private void saveAsToolStripMenuItem_Click(object sender, EventArgs e)
621 | {
622 | using (FileDialog dialog = new SaveFileDialog
623 | {
624 | Title = "Save Image As",
625 | DefaultExt = "png",
626 | Filter = "Portable Networks Graphic (*.png)|*.png|All files (*.*)|*.*"
627 | })
628 | {
629 | if (dialog.ShowDialog(this) == DialogResult.OK)
630 | {
631 | try
632 | {
633 | _transformed.Save(dialog.FileName, ImageFormat.Png);
634 | }
635 | catch (Exception ex)
636 | {
637 | MessageBox.Show(string.Format("Failed to save image. {0}", ex.GetBaseException().Message), "Open Image", MessageBoxButtons.OK, MessageBoxIcon.Error);
638 | }
639 | }
640 | }
641 | }
642 |
643 | private void thresholdNumericUpDown_ValueChanged(object sender, EventArgs e)
644 | {
645 | this.RequestImageTransform();
646 | }
647 |
648 | private void UpdateColorCount(ToolStripItem control, int count)
649 | {
650 | if (this.InvokeRequired)
651 | {
652 | this.Invoke(new Action(this.UpdateColorCount), control, count);
653 | }
654 | else
655 | {
656 | control.Text = count.ToString();
657 | }
658 | }
659 |
660 | private void UpdateRadioSelection(RadioButton control, ref RadioButton previousSelection)
661 | {
662 | // RadioButtons maintain their state per container. However, I'm using multiple
663 | // containers so I need to manually reset the previous selection otherwise
664 | // you will have multiple checked items at once.
665 |
666 | if (control != null && control.Checked)
667 | {
668 | if (!ReferenceEquals(control, previousSelection) && previousSelection != null)
669 | {
670 | previousSelection.Checked = false;
671 | }
672 | previousSelection = control;
673 | }
674 | }
675 |
676 | #endregion
677 | }
678 | }
679 |
--------------------------------------------------------------------------------
/resources/DITHER.TXT:
--------------------------------------------------------------------------------
1 | DITHER.TXT
2 |
3 | What follows is everything you ever wanted to know (for the time being) about
4 | dithering. I'm sure it will be out of date as soon as it is released, but it
5 | does serve to collect data from a wide variety of sources into a single
6 | document, and should save you considerable searching time.
7 |
8 | Numbers in brackets (like this [0]) are references. A list of these works
9 | appears at the end of this document.
10 |
11 | Because this document describes ideas and algorithms which are constantly
12 | changing, I expect that it may have many editions, additions, and corrections
13 | before it gets to you. I will list my name below as original author, but I
14 | do not wish to deter others from adding their own thoughts and discoveries.
15 | This is not copyrighted in any way, and was created solely for the purpose of
16 | organizing my own knowledge on the subject, and sharing this with others.
17 | Please distribute it to anyonw who might be interested.
18 |
19 | If you add anything to this document, please feel free to include your name
20 | below as a contributor or as a reference. I would particularly like to see
21 | additions to the "Other books of interest" section. Please keep the text in
22 | this simple format: no margins, no pagination, no lines longer that 79
23 | characters, and no non-ASCII or non-printing characters other than a CR/LF
24 | pair at the end of each line. It is intended that this be read on as many
25 | different machines as possible.
26 |
27 | Original Author:
28 |
29 | Lee Crocker I can be reached in the CompuServe Graphics
30 | 1380 Jewett Ave Support Forum (GO PICS) with ID # 73407,2030.
31 | Pittsburg, CA 94565
32 |
33 | Contributors:
34 |
35 | ========================================================================
36 | What is Dithering?
37 |
38 | Dithering, also called Halftoning or Color Reduction, is the process of
39 | rendering an image on a display device with fewer colors than are in the
40 | image. The number of different colors in an image or on a device I will call
41 | its Color Resolution. The term "resolution" means "fineness" and is used to
42 | describe the level of detail in a digitally sampled signal. It is used most
43 | often in referring to the Spatial Resolution, which is the basic sampling
44 | rate for a digitized image.
45 |
46 | Spatial resolution describes the fineness of the "dots" used in an image.
47 | Color resolution describes the fineness of detail available at each dot. The
48 | higher the resolution of a digital sample, the better it can reproduce high
49 | frequency detail. A compact disc, for example, has a temporal (time)
50 | resolution of 44,000 samples per second, and a dynamic (volume) resolution of
51 | 16 bits (0..65535). It can therefore reproduce sounds with a vast dynamic
52 | range (from barely audible to ear-splitting) with great detail, but it has
53 | problems with very high-frequency sounds, like violins and piccolos.
54 |
55 | It is often possible to "trade" one kind of resolution for another. If your
56 | display device has a higher spatial resolution than the image you are trying
57 | to reproduce, it can show a very good image even if its color resolution is
58 | less. This is what we will call "dithering" and is the subject of this
59 | paper. The other tradeoff, i.e., trading color resolution for spatial
60 | resolution, is called "anti-aliasing" and is not discussed here.
61 |
62 | It is important to emphasize here that dithering is a one-way operation.
63 | Once an image has been dithered, although it may look like a good
64 | reproduction of the original, information is permanently lost. Many image
65 | processing functions fail on dithered images. For these reasons, dithering
66 | must be considered only as a way to produce an image on hardware that would
67 | otherwise be incapable of displaying it. The data representing an image
68 | should always be kept in full detail.
69 |
70 |
71 | ========================================================================
72 | Classes of dithering algorithms
73 |
74 | The classes of dithering algorithms we will discuss here are these:
75 |
76 | 1. Random
77 | 2. Pattern
78 | 3. Ordered
79 | 4. Error dispersion
80 |
81 | Each of these methods is generally better than those listed before it, but
82 | other considerations such as processing time, memory constraints, etc. may
83 | weigh in favor of one of the simpler methods.
84 |
85 | For the following discussions I will assume that we are given an image with
86 | 256 shades of gray (0=black..255=white) that we are trying to reproduce on a
87 | black and white ouput device. Most of these methods can be extended in
88 | obvious ways to deal with displays that have more than two levels but fewer
89 | than the image, or to color images. Where such extension is not obvious, or
90 | where better results can be obtained, I will go into more detail.
91 |
92 | To convert any of the first three methods into color, simply apply the
93 | algorithm separately for each primary and mix the resulting values. This
94 | assumes that you have at least eight output colors: black, red, green, blue,
95 | cyan, magenta, yellow, and white. Though this will work for error dispersion
96 | as well, there are better methods in this case.
97 |
98 |
99 | ========================================================================
100 | Random dither
101 |
102 | This is the bubblesort of dithering algorithms. It is not really acceptable
103 | as a production method, but it is very simple to describe and implement. For
104 | each value in the image, simply generate a random number 1..256; if it is
105 | geater than the image value at that point, plot the point white, otherwise
106 | plot it black. That's it. This generates a picture with a lot of "white
107 | noise", which looks like TV picture "snow". Though the image produced is
108 | very inaccurate and noisy, it is free from "artifacts" which are phenomena
109 | produced by digital signal processing.
110 |
111 | The most common type of artifact is the Moire pattern (Contributors: please
112 | resist the urge to put an accent on the "e", as no portable character set
113 | exists for this). If you draw several lines close together radiating from a
114 | single point on a computer display, you will see what appear to be flower-
115 | like patterns. These patterns are not part of the original idea of lines,
116 | but are an illusion produced by the jaggedness of the display.
117 |
118 | Many techniques exist for the reduction of digital artifacts like these, most
119 | of which involve using a little randomness to "perturb" a regular algorithm a
120 | little. Random dither obviously takes this to extreme.
121 |
122 | I should mention, of course, that unless your computer has a hardware-based
123 | random number generator (and most don't) there may be some artifacts from the
124 | random number generation algorithm itself.
125 |
126 | While random dither adds a lot of high-frequency noise to a picture, it is
127 | useful in reproducing very low-frequency images where the absence of
128 | artifacts is more important than noise. For example, a whole screen
129 | containing a gradient of all levels from black to white would actually look
130 | best with a random dither. In this case, ordered dithering would produce
131 | diagonal patterns, and error dispersion would produce clustering.
132 |
133 | For efficiency, you can take the random number generator "out of the loop" by
134 | generating a list of random numbers beforehand for use in the dither. Make
135 | sure that the list is larger than the number of pixels in the image or you
136 | may get artifacts from the reuse of numbers. The worst case would be if the
137 | size of your list of random numbers is a multiple or near-multiple of the
138 | horizontal size of the image, in which case unwanted vertical or diagonal
139 | lines will appear.
140 |
141 |
142 | ========================================================================
143 | Pattern dither
144 |
145 | This is also a simple concept, but much more effective than random dither.
146 | For each possible value in the image, create a pattern of dots that
147 | approximates that value. For instance, a 3-by-3 block of dots can have one
148 | of 512 patterns, but for our purposes, there are only 10; the number of black
149 | dots in the pattern determines the darkness of the pattern.
150 |
151 | Which 10 patterns do we choose? Obviously, we need the all-white and all-
152 | black patterns. We can eliminate those patterns which would create vertical
153 | or horizontal lines if repeated over a large area because many images have
154 | such regions of similar value [1]. It has been shown [1] that patterns for
155 | adjacent colors should be similar to reduce an artifact called "contouring",
156 | or visible edges between regions of adjacent values. One easy way to assure
157 | this is to make each pattern a superset of the previous. Here are two good
158 | sets of patterns for a 3-by-3 matrix:
159 |
160 | --- --- --- -X- -XX -XX -XX -XX XXX XXX
161 | --- -X- -XX -XX -XX -XX XXX XXX XXX XXX
162 | --- --- --- --- --- -X- -X- XX- XX- XXX
163 | or
164 | --- X-- X-- X-- X-X X-X X-X XXX XXX XXX
165 | --- --- --- --X --X X-X X-X X-X XXX XXX
166 | --- --- -X- -X- -X- -X- XX- XX- XX- XXX
167 |
168 | The first set of patterns above are "clustered" in that as new dots are added
169 | to each pattern, they are added next to dots already there. The second set
170 | is "dispersed" as the dots are spread out more. This distinction is more
171 | important on larger patterns. Dispersed-dot patterns produce less grainy
172 | images, but require that the output device render each dot distinctly. When
173 | this is not the case, as with a printing press which smears the dots a
174 | little, clustered patterns are better.
175 |
176 | For each pixel in the image we now print the pattern which is closest to its
177 | value. This will triple the size of the image in each direction, so this
178 | method can only be used where the display spatial resolution is much greater
179 | than that of the image.
180 |
181 | We can exploit the fact that most images have large areas of similar value to
182 | reduce our need for extra spatial resolution. Instead of plotting a whole
183 | pattern for each pixel, map each pixel in the image to a dot in the pattern
184 | an only plot the corresponding dot for each pixel.
185 |
186 | The simplest way to do this is to map the X and Y coordinates of each pixel
187 | into the dot (X mod 3, Y mod 3) in the pattern. Large areas of constant
188 | value will come out as repetitions of the pattern as before.
189 |
190 | To extend this method to color images, we must use patterns of colored dots
191 | to represent shades not directly printable by the hardware. For example, if
192 | your hardware is capable of printing only red, green, blue, and black (the
193 | minimal case for color dithering), other colors can be represented with
194 | patterns of these four:
195 |
196 | Yellow = R G Cyan = G B Magenta = R B Gray = R G
197 | G R B G B R B K
198 |
199 | (B here represents blue, K is black). There are a total of 31 such distinct
200 | patterns which can be used; I will leave their enumeration "as an exercise
201 | for the reader" (don't you hate books that do that?).
202 |
203 |
204 | ========================================================================
205 | Ordered dither
206 |
207 | Because each of the patterns above is a superset of the previous, we can
208 | express the patterns in compact form as the order of dots added:
209 |
210 | 8 3 4 and 1 7 4
211 | 6 1 2 5 8 3
212 | 7 5 9 6 2 9
213 |
214 | Then we can simply use the value in the array as a threshhold. If the value
215 | of the pixel (scaled into the 0-9 range) is less than the number in the
216 | corresponding cell of the matrix, plot that pixel black, otherwise, plot it
217 | white. This process is called ordered dither. As before, clustered patterns
218 | should be used for devices which blur dots. In fact, the clustered pattern
219 | ordered dither is the process used by most newspapers, and the term
220 | halftoning refers to this method if not otherwise qualified.
221 |
222 | Bayer [2] has shown that for matrices of orders which are powers of two there
223 | is an optimal pattern of dispersed dots which results in the pattern noise
224 | being as high-frequency as possible. The pattern for a 2x2 and 4x4 matrices
225 | are as follows:
226 |
227 | 1 3 1 9 3 11 These patterns (and their rotations
228 | 4 2 13 5 15 7 and reflections) are optimal for a
229 | 4 12 2 10 dispersed-pattern ordered dither.
230 | 16 8 14 6
231 |
232 | Ulichney [3] shows a recursive technique can be used to generate the larger
233 | patterns. To fully reproduce our 256-level image, we would need to use the
234 | 8x8 pattern.
235 |
236 | Bayer's method is in very common use and is easily identified by the cross-
237 | hatch pattern artifacts it produces in the resulting display. This
238 | artifacting is the major drawback of the technique wich is otherwise very
239 | fast and powerful. Ordered dithering also performs very badly on images
240 | which have already been dithered to some extent. As stated earlier,
241 | dithering should be the last stage in producing a physical display from a
242 | digitally stored image. The dithered image should never be stored itself.
243 |
244 |
245 | ========================================================================
246 | Error dispersion
247 |
248 | This technique generates the best results of any method here, and is
249 | naturally the slowest. In fact, there are many variants of this technique as
250 | well, and the better they get, the slower they are.
251 |
252 | Error dispersion is very simple to describe: for each point in the image,
253 | first find the closest color available. Calculate the difference between the
254 | value in the image and the color you have. Now divide up these error values
255 | and distribute them over the neighboring pixels which you have not visited
256 | yet. When you get to these later pixels, just add the errors distributed
257 | from the earlier ones, clip the values to the allowed range if needed, then
258 | continue as above.
259 |
260 | If you are dithering a grayscale image for output to a black-and-white
261 | device, the "find closest color" is just a simle threshholding operation. In
262 | color, it involves matching the input color to the closest available hardware
263 | color, which can be difficult depending on the hardware palette.
264 |
265 | There are many ways to distribute the errors and many ways to scan the
266 | image, but I will deal here with only a few. The two basic ways to scan the
267 | image are with a normal left-to-right, top-to-bottom raster, or with an
268 | alternating left-to-right then right-to-left raster. The latter method
269 | generally produces fewer artifacts and can be used with all the error
270 | diffusion patterns discussed below.
271 |
272 | The different ways of dividing up the error can be expressed as patterns
273 | (called filters, for reasons too boring to go into here).
274 |
275 | X 7 This is the Floyd and Steinberg [4]
276 | 3 5 1 error diffusion filter.
277 |
278 | In this filter, the X represents the pixel you are currently scanning, and
279 | the numbers (called weights, for equally boring reasons) represent the
280 | proportion of the error distributed to the pixel in that position. Here, the
281 | pixel immediately to the right gets 7/16 of the error (the divisor is 16
282 | because the weights add to 16), the pixel directly below gets 5/16 of the
283 | error, and the diagonally adjacent pixels get 3/16 and 1/16. When scanning a
284 | line right-to-left, this pattern is reversed. This pattern was chosen
285 | carefully so that it would produce a checkerboard pattern in areas with
286 | intensity of 1/2 (or 128 in our image). It is also fairly easy to calculate
287 | when the division by 16 is replaced by shifts.
288 |
289 | Another filter in common use, but not recommended:
290 |
291 | X 3 A simpler filter.
292 | 3 2
293 |
294 | This is often erroneously called the Floyd-Steinberg filter, but it does not
295 | produce as good results. An alternating raster scan of the image is
296 | necessary with this filter to reduce artifacts. Additional perturbations of
297 | the formula are frequently necessary also.
298 |
299 | Burke [5] suggests the following filter:
300 |
301 | X 8 4 The Burke filter.
302 | 2 4 8 4 2
303 |
304 | Notice that this is just a simplification of the Stucki filter (below) with
305 | the bottom row removed. The main improvement is that the divisor is now 32,
306 | which makes calculating the errors faster, and the removal of one row
307 | reduces the memory requirements of the method.
308 |
309 | This is also fairly easy to calculate and produces better results than Floyd
310 | and Steinberg. Jarvis, Judice, and Ninke [6] use the following:
311 |
312 | X 7 5 The Jarvis, et al. pattern.
313 | 3 5 7 5 3
314 | 1 3 5 3 1
315 |
316 | The divisor here is 48, which is a little more expensive to calculate, and
317 | the errors are distributed over three lines, requiring extra memory and time
318 | for processing. Probably the best filter is from Stucki [7]:
319 |
320 | X 8 4 The Stucki pattern.
321 | 2 4 8 4 2
322 | 1 2 4 2 1
323 |
324 | This one takes a division by 42 for each pixel and is therefore slow if math
325 | is done inside the loop. After the initial 8/42 is calculated, some time can
326 | be saved by producing the remaining fractions by shifts.
327 |
328 | The speed advantages of the simpler filters can be eliminated somewhat by
329 | performing the divisions beforehand and using lookup tables instead of per-
330 | forming math inside the loop. This makes it harder to use various filters
331 | in the same program, but the speed benefits are enormous.
332 |
333 | It is critical with all of these algorithms that when error values are added
334 | to neighboring pixels, the values must be truncated to fit within the limits
335 | of hardware, otherwise and area of very intense color may cause streaks into
336 | an adjacent area of less intense color. This truncation adds noise to the
337 | image anagous to clipping in an audio amplifier, but it is not nearly so
338 | offensive as the streaking. It is mainly for this reason that the larger
339 | filters work better--they split the errors up more finely and produce less of
340 | this clipping noise.
341 |
342 | With all of these filters, it is also important to ensure that the errors
343 | you distribute properly add to the original error value. This is easiest to
344 | accomplish by subtracting each fraction from the whole error as it is
345 | calculated, and using the final remainder as the last fraction.
346 |
347 | Some of these methods (particularly the simpler ones) can be greatly improved
348 | by skewing the weights with a little randomness [3].
349 |
350 | Calculating the "nearest available color" is trivial with a monochrome image;
351 | with color images it requires more work. A table of RGB values of all
352 | available colors must be scanned sequentially for each input pixel to find
353 | the closest. The "distance" formula most often used is a simple pythagorean
354 | "least squares". The difference for each color is squared, and the three
355 | squares added to produce the distance value. This value is equivalent to the
356 | square of the distance between the points in RGB-space. It is not necessary
357 | to compute the square root of this value because we are not interested in the
358 | actual distance, only in which is smallest. The square root function is a
359 | monotonic increasing function and does not affect the order of its operands.
360 | If the total number of colors with which you are dealing is small, this part
361 | of the algorithm can be replaced by a lookup table as well.
362 |
363 | When your hardware allows you to select the available colors, very good
364 | results can be achieved by selecting colors from the image itself. You must
365 | reserve at least 8 colors for the primaries, secondaries, black, and white
366 | for best results. If you do not know the colors in your image ahead of time,
367 | or if you are going to use the same map to dither several different images,
368 | you will have to fill your color map with a good range of colors. This can
369 | be done either by assigning a certain number of bits to each primary and
370 | computing all combinations, or by a smoother distribution as suggested by
371 | Heckbert [8].
372 |
373 |
374 | ========================================================================
375 | Sample code
376 |
377 | Despite my best efforts in expository writing, nothing explains an algorithm
378 | better than real code. With that in mind, presented here below is an
379 | algorithm (in somewhat incomplete, very inefficient pseudo-C) which
380 | implements error diffusion dithering with the Floyd and Steinberg filter. It
381 | is not efficiently coded, but its purpose is to show the method, which I
382 | believe it does.
383 |
384 | /* Floyd/Steinberg error diffusion dithering algorithm in color. The array
385 | ** line[][] contains the RGB values for the current line being processed;
386 | ** line[0][x] = red, line[1][x] = green, line[2][x] = blue. It uses the
387 | ** external functions getline() and putdot(), whose pupose should be easy
388 | ** to see from the code.
389 | */
390 |
391 | unsigned char line[3][WIDTH];
392 | unsigned char colormap[3][COLORS] = {
393 | 0, 0, 0, /* Black This color map should be replaced */
394 | 255, 0, 0, /* Red by one available on your hardware. */
395 | 0, 255, 0, /* Green It may contain any number of colors */
396 | 0, 0, 255, /* Blue as long as the constant COLORS is */
397 | 255, 255, 0, /* Yellow set correctly. */
398 | 255, 0, 255, /* Magenta */
399 | 0, 255, 255, /* Cyan */
400 | 255, 255, 255 }; /* White */
401 |
402 | int getline(); /* Function to read line[] from image file; */
403 | /* must return EOF when done. */
404 | putdot(int x, int y, int c); /* Plot dot of color c at location x, y. */
405 |
406 | dither()
407 | {
408 | static int ed[3][WIDTH] = {0}; /* Errors distributed down, i.e., */
409 | /* to the next line. */
410 | int x, y, h, c, nc, v, /* Working variables */
411 | e[4], /* Error parts (7/8,1/8,5/8,3/8). */
412 | ef[3]; /* Error distributed forward. */
413 | long dist, sdist; /* Used for least-squares match. */
414 |
415 | for (x=0; x 255) v = 255; /* and clip. */
428 | line[c][x] = v;
429 | }
430 |
431 | sdist = 255L * 255L * 255L + 1L; /* Compute the color */
432 | for (c=0; c> 1; /* half of v, e[1..4] */
447 | e[1] = (7 * h) >> 3; /* will be filled */
448 | e[2] = h - e[1]; /* with the Floyd and */
449 | h = v - h; /* Steinberg weights. */
450 | e[3] = (5 * h) >> 3;
451 | e[4] = h = e[3];
452 |
453 | ef[c] = e[1]; /* Distribute errors. */
454 | if (x < WIDTH-1) ed[c][x+1] = e[2];
455 | if (x == 0) ed[c][x] = e[3]; else ed[c][x] += e[3];
456 | if (x > 0) ed[c][x-1] += e[4];
457 | }
458 | } /* next x */
459 |
460 | ++y;
461 | } /* next y */
462 | }
463 |
464 |
465 | ========================================================================
466 | Bibliography
467 |
468 | [1] Foley, J. D. and Andries Van Dam (1982)
469 | Fundamentals of Interactive Computer Graphics. Reading, MA: Addisson
470 | Wesley.
471 |
472 | This is a standard reference for many graphic techniques which has not
473 | declined with age. Highly recommended.
474 |
475 | [2] Bayer, B. E. (1973)
476 | "An Optimum Method for Two-Level Rendition of Continuous Tone Pictures,"
477 | IEEE International Conference on Communications, Conference Records, pp.
478 | 26-11 to 26-15.
479 |
480 | A short article proving the optimality of Bayer's pattern in the
481 | dispersed-dot ordered dither.
482 |
483 | [3] Ulichney, R. (1987)
484 | Digital Halftoning. Cambridge, MA: The MIT Press.
485 |
486 | This is the best book I know of for describing the various black and
487 | white dithering methods. It has clear explanations (a little higher math
488 | may come in handy) and wonderful illustrations. It does not contain any
489 | code, but don't let that keep you from getting this book. Computer
490 | Literacy carries it but is often sold out.
491 |
492 | [4] Floyd, R.W. and L. Steinberg (1975)
493 | "An Adaptive Algorithm for Spatial Gray Scale." SID International
494 | Symposium Digest of Technical Papers, vol 1975m, pp. 36-37.
495 |
496 | Short article in which Floyd and Steinberg introduce their filter.
497 |
498 | [5] Daniel Burkes is unpublished, but can be reached at this address:
499 |
500 | Daniel Burkes
501 | TerraVision Inc.
502 | 2351 College Station Road Suite 563
503 | Athens, GA 30305
504 |
505 | or via CompuServe's Graphics Support Forum, ID # 72077,356.
506 |
507 | [6] Jarvis, J. F., C. N. Judice, and W. H. Ninke (1976)
508 | "A Survey of Techniques for the Display of Continuous Tone Pictures on
509 | Bi-Level Displays." Computer Graphics and Image Processing, vol. 5, pp.
510 | 13-40.
511 |
512 | [7] Stucki, P. (1981)
513 | "MECCA - a multiple-error correcting computation algorithm for bilevel
514 | image hardcopy reproduction." Research Report RZ1060, IBM Research
515 | Laboratory, Zurich, Switzerland.
516 |
517 | [8] Heckbert, Paul (9182)
518 | "Color Image Quantization for Frame Buffer Display." Computer Graphics
519 | (SIGGRAPH 82), vol. 16, pp. 297-307.
520 |
521 |
522 | ========================================================================
523 | Other works of interest:
524 |
525 | Newman, William M., and Robert F. S. Sproull (1979)
526 | Principles of Interactive Computer Graphics. 2nd edition. New York:
527 | McGraw-Hill.
528 |
529 | Rogers, David F. (1985)
530 | Procedural Elements for Computer Graphics. New York: McGraw-Hill.
531 |
532 | Rogers, David F., and J. A. Adams (1976)
533 | Mathematical Elements for Computer Graphics. New York: McGraw-Hill.
534 |
535 |
536 | ========================================================================
537 | About CompuServe Graphics Support Forum:
538 |
539 | CompuServe Information Service is a service of the H&R Block companies
540 | providing computer users with electronic mail, teleconferencing, and many
541 | other telecommunications services. Call 800-848-8199 for more information.
542 |
543 | The Graphics Support Forum is dedicated to helping its users get the most out
544 | of their computers' graphics capabilities. It has a small staff and a large
545 | number of "Developers" who create images and software on all types of
546 | machines from Apple IIs to Sun workstations. While on CompuServe, type GO
547 | PICS from any "!" prompt to gain access to the forum.
--------------------------------------------------------------------------------