├── .gitignore
├── LICENSE
├── README.md
├── docs
└── images
│ ├── diffImage.png
│ ├── firstImage.png
│ └── secondImage.png
└── src
├── .nuget
├── NuGet.Config
├── NuGet.exe
└── NuGet.targets
├── ImageDiff.sln
├── ImageDiff
├── AnalyzerTypes.cs
├── Analyzers
│ ├── BitmapAnalyzerFactory.cs
│ ├── CIE76Analyzer.cs
│ ├── CIELab.cs
│ ├── CIExyz.cs
│ ├── ExactMatchAnalyzer.cs
│ └── IBitmapAnalyzer.cs
├── BitmapComparer.cs
├── BoundingBoxModes.cs
├── BoundingBoxes
│ ├── BoundingBoxIdentifierFactory.cs
│ ├── IBoundingBoxIdentifier.cs
│ ├── MultipleBoundingBoxIdentifier.cs
│ └── SingleBoundingBoxIdentifer.cs
├── CompareOptions.cs
├── IImageComparer.cs
├── ImageDiff.csproj
├── LabelerTypes.cs
├── Labelers
│ ├── BasicLabeler.cs
│ ├── ConnectedComponentLabeler.cs
│ ├── IDifferenceLabeler.cs
│ └── LabelerFactory.cs
└── Properties
│ └── AssemblyInfo.cs
└── ImageDiffTests
├── AnalyerFactoryTests.cs
├── BitmapComparerTests.cs
├── BoundingBoxFactoryTests.cs
├── ImageDiffTests.csproj
├── LabelerFactoryTests.cs
├── Properties
└── AssemblyInfo.cs
├── images
├── TestImage1.png
└── TestImage2.png
└── packages.config
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.sln.docstates
8 |
9 | # Build results
10 | [Dd]ebug/
11 | [Dd]ebugPublic/
12 | [Rr]elease/
13 | [Rr]eleases/
14 | x64/
15 | x86/
16 | build/
17 | bld/
18 | [Bb]in/
19 | [Oo]bj/
20 |
21 | # Roslyn cache directories
22 | *.ide/
23 |
24 | # MSTest test Results
25 | [Tt]est[Rr]esult*/
26 | [Bb]uild[Ll]og.*
27 |
28 | #NUNIT
29 | *.VisualState.xml
30 | TestResult.xml
31 |
32 | # Build Results of an ATL Project
33 | [Dd]ebugPS/
34 | [Rr]eleasePS/
35 | dlldata.c
36 |
37 | *_i.c
38 | *_p.c
39 | *_i.h
40 | *.ilk
41 | *.meta
42 | *.obj
43 | *.pch
44 | *.pdb
45 | *.pgc
46 | *.pgd
47 | *.rsp
48 | *.sbr
49 | *.tlb
50 | *.tli
51 | *.tlh
52 | *.tmp
53 | *.tmp_proj
54 | *.log
55 | *.vspscc
56 | *.vssscc
57 | .builds
58 | *.pidb
59 | *.svclog
60 | *.scc
61 |
62 | # Chutzpah Test files
63 | _Chutzpah*
64 |
65 | # Visual C++ cache files
66 | ipch/
67 | *.aps
68 | *.ncb
69 | *.opensdf
70 | *.sdf
71 | *.cachefile
72 |
73 | # Visual Studio profiler
74 | *.psess
75 | *.vsp
76 | *.vspx
77 |
78 | # TFS 2012 Local Workspace
79 | $tf/
80 |
81 | # Guidance Automation Toolkit
82 | *.gpState
83 |
84 | # ReSharper is a .NET coding add-in
85 | _ReSharper*/
86 | *.[Rr]e[Ss]harper
87 | *.DotSettings.user
88 |
89 | # JustCode is a .NET coding addin-in
90 | .JustCode
91 |
92 | # TeamCity is a build add-in
93 | _TeamCity*
94 |
95 | # DotCover is a Code Coverage Tool
96 | *.dotCover
97 |
98 | # NCrunch
99 | _NCrunch_*
100 | .*crunch*.local.xml
101 |
102 | # MightyMoose
103 | *.mm.*
104 | AutoTest.Net/
105 |
106 | # Web workbench (sass)
107 | .sass-cache/
108 |
109 | # Installshield output folder
110 | [Ee]xpress/
111 |
112 | # DocProject is a documentation generator add-in
113 | DocProject/buildhelp/
114 | DocProject/Help/*.HxT
115 | DocProject/Help/*.HxC
116 | DocProject/Help/*.hhc
117 | DocProject/Help/*.hhk
118 | DocProject/Help/*.hhp
119 | DocProject/Help/Html2
120 | DocProject/Help/html
121 |
122 | # Click-Once directory
123 | publish/
124 |
125 | # Publish Web Output
126 | *.[Pp]ublish.xml
127 | *.azurePubxml
128 | # TODO: Comment the next line if you want to checkin your web deploy settings
129 | # but database connection strings (with potential passwords) will be unencrypted
130 | *.pubxml
131 | *.publishproj
132 |
133 | # NuGet Packages
134 | *.nupkg
135 | # The packages folder can be ignored because of Package Restore
136 | **/packages/*
137 | # except build/, which is used as an MSBuild target.
138 | !**/packages/build/
139 | # If using the old MSBuild-Integrated Package Restore, uncomment this:
140 | #!**/packages/repositories.config
141 |
142 | # Windows Azure Build Output
143 | csx/
144 | *.build.csdef
145 |
146 | # Windows Store app package directory
147 | AppPackages/
148 |
149 | # Others
150 | sql/
151 | *.Cache
152 | ClientBin/
153 | [Ss]tyle[Cc]op.*
154 | ~$*
155 | *~
156 | *.dbmdl
157 | *.dbproj.schemaview
158 | *.pfx
159 | *.publishsettings
160 | node_modules/
161 |
162 | # RIA/Silverlight projects
163 | Generated_Code/
164 |
165 | # Backup & report files from converting an old project file
166 | # to a newer Visual Studio version. Backup files are not needed,
167 | # because we have git ;-)
168 | _UpgradeReport_Files/
169 | Backup*/
170 | UpgradeLog*.XML
171 | UpgradeLog*.htm
172 |
173 | # SQL Server files
174 | *.mdf
175 | *.ldf
176 |
177 | # Business Intelligence projects
178 | *.rdl.data
179 | *.bim.layout
180 | *.bim_*.settings
181 |
182 | # Microsoft Fakes
183 | FakesAssemblies/
184 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Richard Clement
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, 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,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://ci.appveyor.com/project/RichClement/imagediff)
2 |
3 | ImageDiff
4 | ==========
5 |
6 | A .Net library for comparing images and highlighting the differences.
7 |
8 | ImageDiff has three stages to its processing.
9 |
10 | 1. Analyze the provided images.
11 | 2. Detect and label the differences between the images.
12 | 3. Build the bounding boxes for the identified blobs.
13 |
14 | After processing is finished, it will generate a new image which is derived from the second image provided to the `Compare` method. This `diff` image will contain highlighted bounding boxes around the differences between the two images.
15 |
16 |  compared to  produces
17 | 
18 |
19 | Usage
20 | -----
21 |
22 | Default usage:
23 |
24 | var firstImage = new Bitmap("path/to/first/image");
25 | var secondImage = new Bitmap("path/to/second/image);
26 |
27 | var comparer = new BitmapComparer();
28 | var diff = comparer.Compare(firstImage, secondImage);
29 |
30 | When initialized without options, the following values are used:
31 |
32 | - AnalyzerType: ExactMatch
33 | - Labeler: Basic
34 | - JustNoticeableDifference: 2.3
35 | - DetectionPadding: 2
36 | - BoundingBoxPadding: 2
37 | - BoundingBoxColor: Red
38 | - BoundingBoxMode: Single
39 |
40 |
41 | The compare object can be configured to use different settings for the different stages of processing.
42 |
43 | var options = new CompareOptions
44 | {
45 | AnalyzerType = AnalyzerTypes.CIE76,
46 | JustNoticableDifference = 2.3,
47 | DetectionPadding = 2,
48 | Labeler = LabelerTypes.ConnectedComponentLabeling,
49 | BoundingBoxColor = Color.Red,
50 | BoundingBoxPadding = 2,
51 | BoundingBoxMode = BoundingBoxModes.Multiple
52 | };
53 | var comparer = new BitmapComparer(options);
54 |
55 | #### Analyzer Type
56 | Two forms of image analysis are currently supported:
57 |
58 | - ExactMatch - requires that the RGB values of each pixel in the image be equal.
59 | - CIE76 - follows the [color difference formula](http://en.wikipedia.org/wiki/Color_difference "color difference formula") to generate a Euclidean distance between the colors in the pixels and flags a difference when the Just Noticeable Difference (JND) is greater than a value of 2.3.
60 |
61 | #### Just Noticeable Difference
62 | Specify this to control how distant two pixels can be in the color space before they are marked as different.
63 |
64 | #### Detection Padding
65 | How many pixels away from the current pixel to look, for neighbors that should be grouped together for labeling purposes.
66 |
67 | #### Labeler
68 | Two forms of blob labeling are currently supported:
69 |
70 | - Basic - basic labeling will group all differences together into a single group. This labeling format does not support `BoundingBoxMode.Multiple`.
71 | - [Connected Component Labeling](http://en.wikipedia.org/wiki/Connected-component_labeling "Connected Component Labeling") - Uses a two-pass algorithm to label the differences in an image and then aggregate the labels. The Detection Padding option is used to determine how far to travel when checking neighbor pixels.
72 |
73 | #### Bounding Box Color
74 | The color of the bounding box to be drawn when highlighting detected differences.
75 |
76 | #### Bounding Box Padding
77 | The number of pixels of padding to include around the detected difference when drawing a bounding box.
78 |
79 | #### Bounding Box Mode
80 | Specifies how to build the bounding boxes when highlighting the detected differences.
81 |
82 | - Single - Only generate one bounding box that encompasses all of the detected differences in the image.
83 | - Multiple - Generate a bounding box around each separate group of detected differences. This bounding box mode is not supported by `LabelerTypes.Basic`.
84 |
--------------------------------------------------------------------------------
/docs/images/diffImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/richclement/ImageDiff/953d4a8bf389475f103003a7e455f80885cab8af/docs/images/diffImage.png
--------------------------------------------------------------------------------
/docs/images/firstImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/richclement/ImageDiff/953d4a8bf389475f103003a7e455f80885cab8af/docs/images/firstImage.png
--------------------------------------------------------------------------------
/docs/images/secondImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/richclement/ImageDiff/953d4a8bf389475f103003a7e455f80885cab8af/docs/images/secondImage.png
--------------------------------------------------------------------------------
/src/.nuget/NuGet.Config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/.nuget/NuGet.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/richclement/ImageDiff/953d4a8bf389475f103003a7e455f80885cab8af/src/.nuget/NuGet.exe
--------------------------------------------------------------------------------
/src/.nuget/NuGet.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(MSBuildProjectDirectory)\..\
5 |
6 |
7 | false
8 |
9 |
10 | false
11 |
12 |
13 | true
14 |
15 |
16 | false
17 |
18 |
19 |
20 |
21 |
22 |
26 |
27 |
28 |
29 |
30 | $([System.IO.Path]::Combine($(SolutionDir), ".nuget"))
31 |
32 |
33 |
34 |
35 | $(SolutionDir).nuget
36 |
37 |
38 |
39 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config
40 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config
41 |
42 |
43 |
44 | $(MSBuildProjectDirectory)\packages.config
45 | $(PackagesProjectConfig)
46 |
47 |
48 |
49 |
50 | $(NuGetToolsPath)\NuGet.exe
51 | @(PackageSource)
52 |
53 | "$(NuGetExePath)"
54 | mono --runtime=v4.0.30319 "$(NuGetExePath)"
55 |
56 | $(TargetDir.Trim('\\'))
57 |
58 | -RequireConsent
59 | -NonInteractive
60 |
61 | "$(SolutionDir) "
62 | "$(SolutionDir)"
63 |
64 |
65 | $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir)
66 | $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols
67 |
68 |
69 |
70 | RestorePackages;
71 | $(BuildDependsOn);
72 |
73 |
74 |
75 |
76 | $(BuildDependsOn);
77 | BuildPackage;
78 |
79 |
80 |
81 |
82 |
83 |
84 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
99 |
100 |
103 |
104 |
105 |
106 |
108 |
109 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
141 |
142 |
143 |
144 |
145 |
--------------------------------------------------------------------------------
/src/ImageDiff.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 2013
4 | VisualStudioVersion = 12.0.31101.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageDiff", "ImageDiff\ImageDiff.csproj", "{8C9C4ABC-4D3A-4DCD-AEE1-95E07E4FAE9F}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageDiffTests", "ImageDiffTests\ImageDiffTests.csproj", "{0B0B0D1B-490B-4A48-9911-A02930F89FD7}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{5D95F6AB-B756-4131-9BAF-E04B01958268}"
11 | ProjectSection(SolutionItems) = preProject
12 | .nuget\NuGet.Config = .nuget\NuGet.Config
13 | .nuget\NuGet.exe = .nuget\NuGet.exe
14 | .nuget\NuGet.targets = .nuget\NuGet.targets
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 | {8C9C4ABC-4D3A-4DCD-AEE1-95E07E4FAE9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24 | {8C9C4ABC-4D3A-4DCD-AEE1-95E07E4FAE9F}.Debug|Any CPU.Build.0 = Debug|Any CPU
25 | {8C9C4ABC-4D3A-4DCD-AEE1-95E07E4FAE9F}.Release|Any CPU.ActiveCfg = Release|Any CPU
26 | {8C9C4ABC-4D3A-4DCD-AEE1-95E07E4FAE9F}.Release|Any CPU.Build.0 = Release|Any CPU
27 | {0B0B0D1B-490B-4A48-9911-A02930F89FD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
28 | {0B0B0D1B-490B-4A48-9911-A02930F89FD7}.Debug|Any CPU.Build.0 = Debug|Any CPU
29 | {0B0B0D1B-490B-4A48-9911-A02930F89FD7}.Release|Any CPU.ActiveCfg = Release|Any CPU
30 | {0B0B0D1B-490B-4A48-9911-A02930F89FD7}.Release|Any CPU.Build.0 = Release|Any CPU
31 | EndGlobalSection
32 | GlobalSection(SolutionProperties) = preSolution
33 | HideSolutionNode = FALSE
34 | EndGlobalSection
35 | EndGlobal
36 |
--------------------------------------------------------------------------------
/src/ImageDiff/AnalyzerTypes.cs:
--------------------------------------------------------------------------------
1 | namespace ImageDiff
2 | {
3 | public enum AnalyzerTypes
4 | {
5 | ExactMatch = 0,
6 | CIE76 = 1
7 | }
8 | }
--------------------------------------------------------------------------------
/src/ImageDiff/Analyzers/BitmapAnalyzerFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace ImageDiff.Analyzers
4 | {
5 | internal static class BitmapAnalyzerFactory
6 | {
7 | public static IBitmapAnalyzer Create(AnalyzerTypes type, double justNoticeableDifference)
8 | {
9 | switch (type)
10 | {
11 | case AnalyzerTypes.ExactMatch:
12 | return new ExactMatchAnalyzer();
13 | case AnalyzerTypes.CIE76:
14 | return new CIE76Analyzer(justNoticeableDifference);
15 | default:
16 | throw new ArgumentException(string.Format("Unrecognized Difference Detection Mode: {0}", type));
17 | }
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/src/ImageDiff/Analyzers/CIE76Analyzer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Drawing;
3 |
4 | namespace ImageDiff.Analyzers
5 | {
6 | internal class CIE76Analyzer : IBitmapAnalyzer
7 | {
8 | private double JustNoticeableDifference { get; set; }
9 |
10 | public CIE76Analyzer(double justNoticeableDifference)
11 | {
12 | JustNoticeableDifference = justNoticeableDifference;
13 | }
14 |
15 | public bool[,] Analyze(Bitmap first, Bitmap second)
16 | {
17 | var diff = new bool[first.Width, first.Height];
18 | for (var x = 0; x < first.Width; x++)
19 | {
20 | for (var y = 0; y < first.Height; y++)
21 | {
22 | var firstLab = CIELab.FromRGB(first.GetPixel(x, y));
23 | var secondLab = CIELab.FromRGB(second.GetPixel(x, y));
24 |
25 | var score = Math.Sqrt(Math.Pow(secondLab.L - firstLab.L, 2) +
26 | Math.Pow(secondLab.a - firstLab.a, 2) +
27 | Math.Pow(secondLab.b - firstLab.b, 2));
28 |
29 | diff[x, y] = (score >= JustNoticeableDifference);
30 | }
31 | }
32 | return diff;
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/ImageDiff/Analyzers/CIELab.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Drawing;
3 |
4 | namespace ImageDiff.Analyzers
5 | {
6 | internal struct CIELab
7 | {
8 | public double L { get; set; }
9 | public double a { get; set; }
10 | public double b { get; set; }
11 |
12 | public CIELab(double l, double a, double b)
13 | : this()
14 | {
15 | this.L = l;
16 | this.a = a;
17 | this.b = b;
18 | }
19 |
20 | public static CIELab FromRGB(Color color)
21 | {
22 | return FromCIExyz(CIExyz.FromRGB(color));
23 | }
24 |
25 | public static CIELab FromCIExyz(CIExyz xyzColor)
26 | {
27 | var transformedX = Transformxyz(xyzColor.x/CIExyz.RefX);
28 | var transformedY = Transformxyz(xyzColor.y/CIExyz.RefY);
29 | var transformedZ = Transformxyz(xyzColor.z/CIExyz.RefZ);
30 |
31 | var L = 116.0 * transformedY - 16;
32 | var a = 500.0 * (transformedX - transformedY);
33 | var b = 200.0 * (transformedY - transformedZ);
34 |
35 | return new CIELab(L, a, b);
36 | }
37 |
38 | private static double Transformxyz(double t)
39 | {
40 | return ((t > 0.008856) ? Math.Pow(t, (1.0 / 3.0)) : ((7.787 * t) + (16.0 / 116.0)));
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/src/ImageDiff/Analyzers/CIExyz.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Drawing;
3 |
4 | namespace ImageDiff.Analyzers
5 | {
6 | internal struct CIExyz
7 | {
8 |
9 | public const double RefX = 95.047;
10 | public const double RefY = 100.000;
11 | public const double RefZ = 108.883;
12 |
13 | public double x { get; set; }
14 | public double y { get; set; }
15 | public double z { get; set; }
16 |
17 | public CIExyz(double x, double y, double z)
18 | : this()
19 | {
20 | this.x = x;
21 | this.y = y;
22 | this.z = z;
23 | }
24 |
25 | public static CIExyz FromRGB(Color color)
26 | {
27 | // normalize red, green, blue values
28 | var rLinear = color.R / 255.0;
29 | var gLinear = color.G / 255.0;
30 | var bLinear = color.B / 255.0;
31 |
32 | // convert to a sRGB form
33 | var r = (rLinear > 0.04045) ? Math.Pow((rLinear + 0.055) / (1.055), 2.4) : (rLinear / 12.92);
34 | var g = (gLinear > 0.04045) ? Math.Pow((gLinear + 0.055) / (1.055), 2.4) : (gLinear / 12.92);
35 | var b = (bLinear > 0.04045) ? Math.Pow((bLinear + 0.055) / (1.055), 2.4) : (bLinear / 12.92);
36 |
37 | // converts
38 | return new CIExyz((r * 0.4124 + g * 0.3576 + b * 0.1805),
39 | (r * 0.2126 + g * 0.7152 + b * 0.0722),
40 | (r * 0.0193 + g * 0.1192 + b * 0.9505));
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/src/ImageDiff/Analyzers/ExactMatchAnalyzer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Drawing;
3 |
4 | namespace ImageDiff.Analyzers
5 | {
6 | internal class ExactMatchAnalyzer : IBitmapAnalyzer
7 | {
8 | public bool[,] Analyze(Bitmap first, Bitmap second)
9 | {
10 | var diff = new bool[first.Width, first.Height];
11 | for (var x = 0; x < first.Width; x++)
12 | {
13 | for (var y = 0; y < first.Height; y++)
14 | {
15 | var firstPixel = first.GetPixel(x, y);
16 | var secondPixel = second.GetPixel(x, y);
17 | if (firstPixel != secondPixel)
18 | {
19 | diff[x, y] = true;
20 | }
21 | }
22 | }
23 | return diff;
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/src/ImageDiff/Analyzers/IBitmapAnalyzer.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 |
3 | namespace ImageDiff.Analyzers
4 | {
5 | internal interface IBitmapAnalyzer
6 | {
7 | bool[,] Analyze(Bitmap first, Bitmap second);
8 | }
9 | }
--------------------------------------------------------------------------------
/src/ImageDiff/BitmapComparer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Drawing;
4 | using System.Linq;
5 | using ImageDiff.Analyzers;
6 | using ImageDiff.BoundingBoxes;
7 | using ImageDiff.Labelers;
8 |
9 | namespace ImageDiff
10 | {
11 | public class BitmapComparer : IImageComparer
12 | {
13 | private LabelerTypes LabelerType { get; set; }
14 | private double JustNoticeableDifference { get; set; }
15 | private int DetectionPadding { get; set; }
16 | private int BoundingBoxPadding { get; set; }
17 | private Color BoundingBoxColor { get; set; }
18 | private BoundingBoxModes BoundingBoxMode { get; set; }
19 | private AnalyzerTypes AnalyzerType { get; set; }
20 |
21 | private IDifferenceLabeler Labeler { get; set; }
22 | private IBoundingBoxIdentifier BoundingBoxIdentifier { get; set; }
23 | private IBitmapAnalyzer BitmapAnalyzer { get; set; }
24 |
25 | public BitmapComparer(CompareOptions options = null)
26 | {
27 | if (options == null)
28 | {
29 | options = new CompareOptions();
30 | }
31 | Initialize(options);
32 |
33 | BitmapAnalyzer = BitmapAnalyzerFactory.Create(AnalyzerType, JustNoticeableDifference);
34 | Labeler = LabelerFactory.Create(LabelerType, DetectionPadding);
35 | BoundingBoxIdentifier = BoundingBoxIdentifierFactory.Create(BoundingBoxMode, BoundingBoxPadding);
36 | }
37 |
38 | private void Initialize(CompareOptions options)
39 | {
40 | if (options.BoundingBoxPadding < 0) throw new ArgumentException("bounding box padding must be non-negative");
41 | if (options.DetectionPadding < 0) throw new ArgumentException("detection padding must be non-negative");
42 |
43 | LabelerType = options.Labeler;
44 | JustNoticeableDifference = options.JustNoticeableDifference;
45 | BoundingBoxColor = options.BoundingBoxColor;
46 | DetectionPadding = options.DetectionPadding;
47 | BoundingBoxPadding = options.BoundingBoxPadding;
48 | BoundingBoxMode = options.BoundingBoxMode;
49 | AnalyzerType = options.AnalyzerType;
50 | }
51 |
52 | public Bitmap Compare(Bitmap firstImage, Bitmap secondImage)
53 | {
54 | if (firstImage == null) throw new ArgumentNullException("firstImage");
55 | if (secondImage == null) throw new ArgumentNullException("secondImage");
56 | if (firstImage.Width != secondImage.Width || firstImage.Height != secondImage.Height) throw new ArgumentException("Bitmaps must be the same size.");
57 |
58 | var differenceMap = BitmapAnalyzer.Analyze(firstImage, secondImage);
59 | var differenceLabels = Labeler.Label(differenceMap);
60 | var boundingBoxes = BoundingBoxIdentifier.CreateBoundingBoxes(differenceLabels);
61 | var differenceBitmap = CreateImageWithBoundingBoxes(secondImage, boundingBoxes);
62 | return differenceBitmap;
63 | }
64 |
65 | public bool Equals(Bitmap firstImage, Bitmap secondImage)
66 | {
67 | if (firstImage == null && secondImage == null) return true;
68 | if (firstImage == null) return false;
69 | if (secondImage == null) return false;
70 | if (firstImage.Width != secondImage.Width || firstImage.Height != secondImage.Height) return false;
71 |
72 | var differenceMap = BitmapAnalyzer.Analyze(firstImage, secondImage);
73 |
74 | // differenceMap is a 2d array of boolean values, true represents a difference between the images
75 | // iterate over the dimensions of the array and look for a true value (difference) and return false
76 | for (var i = 0; i < differenceMap.GetLength(0); i++)
77 | {
78 | for (var j = 0; j < differenceMap.GetLength(1); j++)
79 | {
80 | if (differenceMap[i, j])
81 | return false;
82 | }
83 | }
84 | return true;
85 | }
86 |
87 | private Bitmap CreateImageWithBoundingBoxes(Bitmap secondImage, IEnumerable boundingBoxes)
88 | {
89 | var differenceBitmap = secondImage.Clone() as Bitmap;
90 | if (differenceBitmap == null) throw new Exception("Could not copy secondImage");
91 |
92 | var boundingRectangles = boundingBoxes.ToArray();
93 | if (boundingRectangles.Length == 0)
94 | return differenceBitmap;
95 |
96 | using (var g = Graphics.FromImage(differenceBitmap))
97 | {
98 | var pen = new Pen(BoundingBoxColor);
99 | foreach (var boundingRectangle in boundingRectangles)
100 | {
101 | g.DrawRectangle(pen, boundingRectangle);
102 | }
103 | }
104 | return differenceBitmap;
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/ImageDiff/BoundingBoxModes.cs:
--------------------------------------------------------------------------------
1 | namespace ImageDiff
2 | {
3 | public enum BoundingBoxModes
4 | {
5 | Single = 0,
6 | Multiple = 1
7 | }
8 | }
--------------------------------------------------------------------------------
/src/ImageDiff/BoundingBoxes/BoundingBoxIdentifierFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace ImageDiff.BoundingBoxes
4 | {
5 | internal static class BoundingBoxIdentifierFactory
6 | {
7 | public static IBoundingBoxIdentifier Create(BoundingBoxModes mode, int padding)
8 | {
9 | switch (mode)
10 | {
11 | case BoundingBoxModes.Single:
12 | return new SingleBoundingBoxIdentifer(padding);
13 | case BoundingBoxModes.Multiple:
14 | return new MultipleBoundingBoxIdentifier(padding);
15 | default:
16 | throw new ArgumentException(string.Format("Unrecognized Bounding Box Mode: {0}", mode));
17 | }
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/src/ImageDiff/BoundingBoxes/IBoundingBoxIdentifier.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Drawing;
3 |
4 | namespace ImageDiff.BoundingBoxes
5 | {
6 | internal interface IBoundingBoxIdentifier
7 | {
8 | IEnumerable CreateBoundingBoxes(int[,] labelMap);
9 | }
10 | }
--------------------------------------------------------------------------------
/src/ImageDiff/BoundingBoxes/MultipleBoundingBoxIdentifier.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Drawing;
4 | using System.Linq;
5 |
6 | namespace ImageDiff.BoundingBoxes
7 | {
8 | internal class MultipleBoundingBoxIdentifier : IBoundingBoxIdentifier
9 | {
10 | private int Padding { get; set; }
11 |
12 | public MultipleBoundingBoxIdentifier(int padding)
13 | {
14 | Padding = padding;
15 | }
16 |
17 | public IEnumerable CreateBoundingBoxes(int[,] labelMap)
18 | {
19 | var boundedPoints = FindLabeledPointGroups(labelMap);
20 | var boundingRectangles = CreateBoundingBoxes(boundedPoints);
21 | return boundingRectangles;
22 | }
23 |
24 | private IEnumerable CreateBoundingBoxes(Dictionary> boundedPoints)
25 | {
26 | if (boundedPoints == null || boundedPoints.Count == 0)
27 | yield break;
28 |
29 | foreach (var kvp in boundedPoints)
30 | {
31 | var points = kvp.Value;
32 | var minPoint = new Point(points.Min(x => x.X), points.Min(y => y.Y));
33 | var maxPoint = new Point(points.Max(x => x.X), points.Max(y => y.Y));
34 | var rectangle = new Rectangle(minPoint.X - Padding,
35 | minPoint.Y - Padding,
36 | (maxPoint.X - minPoint.X) + (Padding * 2),
37 | (maxPoint.Y - minPoint.Y) + (Padding * 2));
38 |
39 | yield return rectangle;
40 | }
41 | }
42 |
43 | private static Dictionary> FindLabeledPointGroups(int[,] labelMap)
44 | {
45 | var width = labelMap.GetLength(0);
46 | var height = labelMap.GetLength(1);
47 |
48 | var boundedPoints = new Dictionary>();
49 | for (var x = 0; x < width; x++)
50 | {
51 | for (var y = 0; y < height; y++)
52 | {
53 | if (labelMap[x, y] == 0) continue;
54 |
55 | var label = labelMap[x, y];
56 | if (!boundedPoints.ContainsKey(label))
57 | boundedPoints.Add(label, new List { new Point(x, y) });
58 | else
59 | boundedPoints[label].Add(new Point(x, y));
60 | }
61 | }
62 | return boundedPoints;
63 | }
64 | }
65 | }
--------------------------------------------------------------------------------
/src/ImageDiff/BoundingBoxes/SingleBoundingBoxIdentifer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Drawing;
4 | using System.Linq;
5 |
6 | namespace ImageDiff.BoundingBoxes
7 | {
8 | internal class SingleBoundingBoxIdentifer : IBoundingBoxIdentifier
9 | {
10 | private int Padding { get; set; }
11 |
12 | public SingleBoundingBoxIdentifer(int padding)
13 | {
14 | Padding = padding;
15 | }
16 |
17 | public IEnumerable CreateBoundingBoxes(int[,] labelMap)
18 | {
19 | var points = FindLabeledPoints(labelMap);
20 |
21 | if (!points.Any())
22 | return new List();
23 |
24 | var minPoint = new Point(points.Min(x => x.X), points.Min(y => y.Y));
25 | var maxPoint = new Point(points.Max(x => x.X), points.Max(y => y.Y));
26 |
27 | var rectangle = new Rectangle(minPoint.X - Padding,
28 | minPoint.Y - Padding,
29 | (maxPoint.X - minPoint.X) + (Padding * 2),
30 | (maxPoint.Y - minPoint.Y) + (Padding * 2));
31 |
32 | return new List { rectangle };
33 | }
34 |
35 | private static List FindLabeledPoints(int[,] labelMap)
36 | {
37 | var width = labelMap.GetLength(0);
38 | var height = labelMap.GetLength(1);
39 | var points = new List();
40 | for (var x = 0; x < width; x++)
41 | {
42 | for (var y = 0; y < height; y++)
43 | {
44 | if (labelMap[x, y] > 0)
45 | points.Add(new Point(x, y));
46 | }
47 | }
48 | return points;
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/src/ImageDiff/CompareOptions.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 |
3 | namespace ImageDiff
4 | {
5 | public class CompareOptions
6 | {
7 | public AnalyzerTypes AnalyzerType { get; set; }
8 | public LabelerTypes Labeler { get; set; }
9 | public double JustNoticeableDifference { get; set; }
10 | public int DetectionPadding { get; set; }
11 | public int BoundingBoxPadding { get; set; }
12 | public Color BoundingBoxColor { get; set; }
13 | public BoundingBoxModes BoundingBoxMode { get; set; }
14 |
15 | public CompareOptions()
16 | {
17 | Labeler = LabelerTypes.Basic;
18 | JustNoticeableDifference = 2.3;
19 | DetectionPadding = 2;
20 | BoundingBoxPadding = 2;
21 | BoundingBoxColor = Color.Red;
22 | BoundingBoxMode = BoundingBoxModes.Single;
23 | AnalyzerType = AnalyzerTypes.ExactMatch;
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/src/ImageDiff/IImageComparer.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 |
3 | namespace ImageDiff
4 | {
5 | public interface IImageComparer where T : Image
6 | {
7 | T Compare(T firstImage, T secondImage);
8 | bool Equals(T firstImage, T secondImage);
9 | }
10 | }
--------------------------------------------------------------------------------
/src/ImageDiff/ImageDiff.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {8C9C4ABC-4D3A-4DCD-AEE1-95E07E4FAE9F}
8 | Library
9 | Properties
10 | ImageDiff
11 | ImageDiff
12 | v4.5
13 | 512
14 |
15 |
16 | true
17 | full
18 | false
19 | bin\Debug\
20 | DEBUG;TRACE
21 | prompt
22 | 4
23 |
24 |
25 | pdbonly
26 | true
27 | bin\Release\
28 | TRACE
29 | prompt
30 | 4
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
68 |
--------------------------------------------------------------------------------
/src/ImageDiff/LabelerTypes.cs:
--------------------------------------------------------------------------------
1 | namespace ImageDiff
2 | {
3 | public enum LabelerTypes
4 | {
5 | Basic = 0,
6 | ConnectedComponentLabeling = 1
7 | }
8 | }
--------------------------------------------------------------------------------
/src/ImageDiff/Labelers/BasicLabeler.cs:
--------------------------------------------------------------------------------
1 | namespace ImageDiff.Labelers
2 | {
3 | internal class BasicLabeler : IDifferenceLabeler
4 | {
5 | public int[,] Label(bool[,] differenceMap)
6 | {
7 | var width = differenceMap.GetLength(0);
8 | var height = differenceMap.GetLength(1);
9 | var analyzedMap = new int[width, height];
10 |
11 | for (var x = 0; x < width; x++)
12 | {
13 | for (var y = 0; y < height; y++)
14 | {
15 | if (differenceMap[x, y])
16 | analyzedMap[x, y] = 1;
17 | }
18 | }
19 | return analyzedMap;
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/src/ImageDiff/Labelers/ConnectedComponentLabeler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Drawing;
4 | using System.Linq;
5 |
6 | namespace ImageDiff.Labelers
7 | {
8 | internal class ConnectedComponentLabeler : IDifferenceLabeler
9 | {
10 | private int Padding { get; set; }
11 | private int[,] Labels { get; set; }
12 | private Dictionary> Linked { get; set; }
13 |
14 | public ConnectedComponentLabeler(int padding)
15 | {
16 | Padding = padding;
17 | Linked = new Dictionary>();
18 | }
19 |
20 | public int[,] Label(bool[,] differenceMap)
21 | {
22 | var width = differenceMap.GetLength(0);
23 | var height = differenceMap.GetLength(1);
24 |
25 | Labels = new int[width, height];
26 |
27 | var nextLabel = 1;
28 | for (var y = 0; y < height; y++)
29 | {
30 | for (var x = 0; x < width; x++)
31 | {
32 | if (differenceMap[x, y] == false) continue;
33 |
34 | var neighbors = Neighbors(differenceMap, x, y);
35 | if (neighbors == null || neighbors.Count == 0)
36 | {
37 | Linked.Add(nextLabel, new List { nextLabel });
38 | Labels[x, y] = nextLabel;
39 | nextLabel += 1;
40 | }
41 | else
42 | {
43 | var neighborsLabels = NeighborsLabels(neighbors);
44 | Labels[x, y] = neighborsLabels.Min();
45 | foreach (var label in neighborsLabels)
46 | {
47 | Linked[label] = Linked[label].Union(neighborsLabels).ToList();
48 | }
49 | }
50 | }
51 | }
52 |
53 | // second pass
54 | for (var y = 0; y < height; y++)
55 | {
56 | for (var x = 0; x < width; x++)
57 | {
58 | var currentLabel = Labels[x, y];
59 | if (currentLabel == 0) continue;
60 |
61 | Labels[x, y] = FindLowestEquivalentLabel(currentLabel);
62 | }
63 | }
64 |
65 | return Labels;
66 | }
67 |
68 | private int FindLowestEquivalentLabel(int currentLabel)
69 | {
70 | var equivalentLabels = Linked[currentLabel];
71 | return equivalentLabels.Min();
72 | }
73 |
74 | private List NeighborsLabels(IEnumerable neighbors)
75 | {
76 | return neighbors.Select(n => Labels[n.X, n.Y]).ToList();
77 | }
78 |
79 | private List Neighbors(bool[,] bitmap, int x, int y)
80 | {
81 | var points = GenerateNeighbors(bitmap.GetLength(0), x, y);
82 | return points.Where(p => bitmap[p.X, p.Y]).ToList();
83 | }
84 |
85 | private IEnumerable GenerateNeighbors(int width, int x, int y)
86 | {
87 | var points = new List();
88 | var counter = 0;
89 | while (counter <= Padding)
90 | {
91 | var offset = counter + 1;
92 |
93 | if (x > counter)
94 | {
95 | points.Add(new Point(x - offset, y));
96 | if (y > counter) points.Add(new Point(x - offset, y - offset));
97 | }
98 | if (y > counter)
99 | {
100 | points.Add(new Point(x, y - offset));
101 | if (x < (width - offset)) points.Add(new Point(x + offset, y - offset));
102 | }
103 | counter += 1;
104 | }
105 | return points;
106 | }
107 | }
108 | }
--------------------------------------------------------------------------------
/src/ImageDiff/Labelers/IDifferenceLabeler.cs:
--------------------------------------------------------------------------------
1 | namespace ImageDiff.Labelers
2 | {
3 | internal interface IDifferenceLabeler
4 | {
5 | int[,] Label(bool[,] differenceMap);
6 | }
7 | }
--------------------------------------------------------------------------------
/src/ImageDiff/Labelers/LabelerFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace ImageDiff.Labelers
4 | {
5 | internal static class LabelerFactory
6 | {
7 | public static IDifferenceLabeler Create(LabelerTypes types, int padding)
8 | {
9 | switch (types)
10 | {
11 | case LabelerTypes.Basic:
12 | return new BasicLabeler();
13 | case LabelerTypes.ConnectedComponentLabeling:
14 | return new ConnectedComponentLabeler(padding);
15 | default:
16 | throw new ArgumentException(string.Format("Unrecognized Analyzer Type: {0}", types));
17 | }
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/src/ImageDiff/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("ImageDiff")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("ImageDiff")]
13 | [assembly: AssemblyCopyright("")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | [assembly: InternalsVisibleTo("ImageDiffTests")]
18 |
19 | // Setting ComVisible to false makes the types in this assembly not visible
20 | // to COM components. If you need to access a type in this assembly from
21 | // COM, set the ComVisible attribute to true on that type.
22 | [assembly: ComVisible(false)]
23 |
24 | // The following GUID is for the ID of the typelib if this project is exposed to COM
25 | [assembly: Guid("4e462185-6889-4657-a2a9-b3540463fa51")]
26 |
27 | // Version information for an assembly consists of the following four values:
28 | //
29 | // Major Version
30 | // Minor Version
31 | // Build Number
32 | // Revision
33 | //
34 | // You can specify all the values or you can default the Build and Revision Numbers
35 | // by using the '*' as shown below:
36 | // [assembly: AssemblyVersion("1.0.*")]
37 | [assembly: AssemblyVersion("1.0.0.0")]
38 | [assembly: AssemblyFileVersion("1.0.0.0")]
39 |
--------------------------------------------------------------------------------
/src/ImageDiffTests/AnalyerFactoryTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using ImageDiff;
3 | using ImageDiff.Analyzers;
4 | using NUnit.Framework;
5 |
6 | namespace ImageDiffTests
7 | {
8 | [TestFixture]
9 | public class AnalyerFactoryTests
10 | {
11 | [Test, ExpectedException(typeof(ArgumentException))]
12 | public void FactoryThrowsWithInvalidType()
13 | {
14 | BitmapAnalyzerFactory.Create((AnalyzerTypes)100, 2.3);
15 | }
16 |
17 | [Test]
18 | public void FactoryCreatesExactMatchAnalyzer()
19 | {
20 | var target = BitmapAnalyzerFactory.Create(AnalyzerTypes.ExactMatch, 2.3);
21 | Assert.IsInstanceOf(typeof(ExactMatchAnalyzer), target);
22 | }
23 |
24 | [Test]
25 | public void FactoryCreatesCIE76Analyzer()
26 | {
27 | var target = BitmapAnalyzerFactory.Create(AnalyzerTypes.CIE76, 2.3);
28 | Assert.IsInstanceOf(typeof(CIE76Analyzer), target);
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/ImageDiffTests/BitmapComparerTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Drawing;
3 | using ImageDiff;
4 | using NUnit.Framework;
5 |
6 | namespace ImageDiffTests
7 | {
8 | [TestFixture]
9 | public class BitmapComparerTests
10 | {
11 | protected const string TestImage1 = "./images/TestImage1.png";
12 | protected const string TestImage2 = "./images/TestImage2.png";
13 | protected const string OutputFormat = "output_{0}.png";
14 | protected Bitmap FirstImage { get; set; }
15 | protected Bitmap SecondImage { get; set; }
16 |
17 | [SetUp]
18 | public void Setup()
19 | {
20 | FirstImage = new Bitmap(TestImage1);
21 | SecondImage = new Bitmap(TestImage2);
22 | }
23 |
24 | [Test, ExpectedException(typeof(ArgumentNullException))]
25 | public void CompareThrowsWhenFirstImageIsNull()
26 | {
27 | var target = new BitmapComparer(null);
28 | target.Compare(null, SecondImage);
29 | }
30 |
31 | [Test, ExpectedException(typeof(ArgumentNullException))]
32 | public void CompareThrowsWhenSecondImageIsNull()
33 | {
34 | var target = new BitmapComparer(null);
35 | target.Compare(FirstImage, null);
36 | }
37 |
38 | [Test, ExpectedException(typeof(ArgumentException))]
39 | public void CompareThrowsWhenImagesAreNotSameWidth()
40 | {
41 | var firstBitmap = new Bitmap(10, 10);
42 | var secondBitmap = new Bitmap(20, 10);
43 |
44 | var target = new BitmapComparer(null);
45 | target.Compare(firstBitmap, secondBitmap);
46 | }
47 |
48 | [Test, ExpectedException(typeof(ArgumentException))]
49 | public void CompareThrowsWhenImagesAreNotSameHeight()
50 | {
51 | var firstBitmap = new Bitmap(10, 10);
52 | var secondBitmap = new Bitmap(10, 20);
53 |
54 | var target = new BitmapComparer(null);
55 | target.Compare(firstBitmap, secondBitmap);
56 | }
57 |
58 | [Test, ExpectedException(typeof(ArgumentException))]
59 | public void ImageDiffThrowsWhenBoundingBoxPaddingIsLessThanZero()
60 | {
61 | var target = new BitmapComparer(new CompareOptions
62 | {
63 | BoundingBoxColor = Color.Red,
64 | BoundingBoxMode = BoundingBoxModes.Single,
65 | AnalyzerType = AnalyzerTypes.ExactMatch,
66 | DetectionPadding = 2,
67 | BoundingBoxPadding = -2,
68 | Labeler = LabelerTypes.Basic
69 | });
70 | }
71 |
72 | [Test, ExpectedException(typeof(ArgumentException))]
73 | public void ImageDiffThrowsWhenDetectionPaddingIsLessThanZero()
74 | {
75 | var target = new BitmapComparer(new CompareOptions
76 | {
77 | BoundingBoxColor = Color.Red,
78 | BoundingBoxMode = BoundingBoxModes.Single,
79 | AnalyzerType = AnalyzerTypes.ExactMatch,
80 | DetectionPadding = -2,
81 | BoundingBoxPadding = 2,
82 | Labeler = LabelerTypes.Basic
83 | });
84 | }
85 |
86 | [Test]
87 | public void CompareWorksWithNoOptions()
88 | {
89 | var target = new BitmapComparer();
90 | var result = target.Compare(FirstImage, SecondImage);
91 | result.Save(string.Format(OutputFormat, "CompareWorksWithNullOptions"), SecondImage.RawFormat);
92 | }
93 |
94 | [Test]
95 | public void CompareWorksWithNullOptions()
96 | {
97 | var target = new BitmapComparer(null);
98 | var result = target.Compare(FirstImage, SecondImage);
99 | result.Save(string.Format(OutputFormat, "CompareWorksWithNullOptions"), SecondImage.RawFormat);
100 | }
101 |
102 | [TestCase(AnalyzerTypes.CIE76, BoundingBoxModes.Single, LabelerTypes.Basic)]
103 | [TestCase(AnalyzerTypes.CIE76, BoundingBoxModes.Single, LabelerTypes.ConnectedComponentLabeling)]
104 | [TestCase(AnalyzerTypes.CIE76, BoundingBoxModes.Multiple, LabelerTypes.Basic)]
105 | [TestCase(AnalyzerTypes.CIE76, BoundingBoxModes.Multiple, LabelerTypes.ConnectedComponentLabeling)]
106 | [TestCase(AnalyzerTypes.ExactMatch, BoundingBoxModes.Single, LabelerTypes.Basic)]
107 | [TestCase(AnalyzerTypes.ExactMatch, BoundingBoxModes.Single, LabelerTypes.ConnectedComponentLabeling)]
108 | [TestCase(AnalyzerTypes.ExactMatch, BoundingBoxModes.Multiple, LabelerTypes.Basic)]
109 | [TestCase(AnalyzerTypes.ExactMatch, BoundingBoxModes.Multiple, LabelerTypes.ConnectedComponentLabeling)]
110 | public void CompareWorksWithIdenticalImages(AnalyzerTypes aType, BoundingBoxModes bMode, LabelerTypes lType)
111 | {
112 | var target = new BitmapComparer(new CompareOptions
113 | {
114 | AnalyzerType = aType,
115 | BoundingBoxMode = bMode,
116 | Labeler = lType
117 | });
118 | var result = target.Compare(FirstImage, FirstImage);
119 | result.Save(string.Format(OutputFormat, string.Format("CompareWorksWithIdenticalImages_{0}_{1}_{2}", aType, bMode, lType)),
120 | SecondImage.RawFormat);
121 | }
122 |
123 |
124 | [TestCase(AnalyzerTypes.CIE76, BoundingBoxModes.Single, LabelerTypes.Basic)]
125 | [TestCase(AnalyzerTypes.CIE76, BoundingBoxModes.Single, LabelerTypes.ConnectedComponentLabeling)]
126 | [TestCase(AnalyzerTypes.CIE76, BoundingBoxModes.Multiple, LabelerTypes.Basic)]
127 | [TestCase(AnalyzerTypes.CIE76, BoundingBoxModes.Multiple, LabelerTypes.ConnectedComponentLabeling)]
128 | [TestCase(AnalyzerTypes.ExactMatch, BoundingBoxModes.Single, LabelerTypes.Basic)]
129 | [TestCase(AnalyzerTypes.ExactMatch, BoundingBoxModes.Single, LabelerTypes.ConnectedComponentLabeling)]
130 | [TestCase(AnalyzerTypes.ExactMatch, BoundingBoxModes.Multiple, LabelerTypes.Basic)]
131 | [TestCase(AnalyzerTypes.ExactMatch, BoundingBoxModes.Multiple, LabelerTypes.ConnectedComponentLabeling)]
132 | public void CompareWorksWithDifferentImages(AnalyzerTypes aType, BoundingBoxModes bMode, LabelerTypes lType)
133 | {
134 | var target = new BitmapComparer(new CompareOptions
135 | {
136 | BoundingBoxColor = Color.Red,
137 | BoundingBoxMode = bMode,
138 | AnalyzerType = aType,
139 | DetectionPadding = 2,
140 | BoundingBoxPadding = 2,
141 | Labeler = lType
142 | });
143 | var result = target.Compare(FirstImage, SecondImage);
144 | result.Save(string.Format(OutputFormat, string.Format("CompareWorksWithDifferentImages_{0}_{1}_{2}", aType, bMode, lType)), SecondImage.RawFormat);
145 | }
146 |
147 | [TestCase(AnalyzerTypes.CIE76, BoundingBoxModes.Single, LabelerTypes.Basic)]
148 | [TestCase(AnalyzerTypes.CIE76, BoundingBoxModes.Single, LabelerTypes.ConnectedComponentLabeling)]
149 | [TestCase(AnalyzerTypes.CIE76, BoundingBoxModes.Multiple, LabelerTypes.Basic)]
150 | [TestCase(AnalyzerTypes.CIE76, BoundingBoxModes.Multiple, LabelerTypes.ConnectedComponentLabeling)]
151 | [TestCase(AnalyzerTypes.ExactMatch, BoundingBoxModes.Single, LabelerTypes.Basic)]
152 | [TestCase(AnalyzerTypes.ExactMatch, BoundingBoxModes.Single, LabelerTypes.ConnectedComponentLabeling)]
153 | [TestCase(AnalyzerTypes.ExactMatch, BoundingBoxModes.Multiple, LabelerTypes.Basic)]
154 | [TestCase(AnalyzerTypes.ExactMatch, BoundingBoxModes.Multiple, LabelerTypes.ConnectedComponentLabeling)]
155 | public void EqualsReturnsTrueWithSameImage(AnalyzerTypes aType, BoundingBoxModes bMode, LabelerTypes lType)
156 | {
157 | var target = new BitmapComparer(new CompareOptions
158 | {
159 | BoundingBoxColor = Color.Red,
160 | BoundingBoxMode = bMode,
161 | AnalyzerType = aType,
162 | DetectionPadding = 2,
163 | BoundingBoxPadding = 2,
164 | Labeler = lType
165 | });
166 | var newInstanceOfFirstImage = new Bitmap(TestImage1);
167 | var result = target.Equals(FirstImage, newInstanceOfFirstImage);
168 | Assert.IsTrue(result);
169 | }
170 |
171 | [TestCase(AnalyzerTypes.CIE76, BoundingBoxModes.Single, LabelerTypes.Basic)]
172 | [TestCase(AnalyzerTypes.CIE76, BoundingBoxModes.Single, LabelerTypes.ConnectedComponentLabeling)]
173 | [TestCase(AnalyzerTypes.CIE76, BoundingBoxModes.Multiple, LabelerTypes.Basic)]
174 | [TestCase(AnalyzerTypes.CIE76, BoundingBoxModes.Multiple, LabelerTypes.ConnectedComponentLabeling)]
175 | [TestCase(AnalyzerTypes.ExactMatch, BoundingBoxModes.Single, LabelerTypes.Basic)]
176 | [TestCase(AnalyzerTypes.ExactMatch, BoundingBoxModes.Single, LabelerTypes.ConnectedComponentLabeling)]
177 | [TestCase(AnalyzerTypes.ExactMatch, BoundingBoxModes.Multiple, LabelerTypes.Basic)]
178 | [TestCase(AnalyzerTypes.ExactMatch, BoundingBoxModes.Multiple, LabelerTypes.ConnectedComponentLabeling)]
179 | public void EqualsReturnsFalseWithDifferentImage(AnalyzerTypes aType, BoundingBoxModes bMode, LabelerTypes lType)
180 | {
181 | var target = new BitmapComparer(new CompareOptions
182 | {
183 | BoundingBoxColor = Color.Red,
184 | BoundingBoxMode = bMode,
185 | AnalyzerType = aType,
186 | DetectionPadding = 2,
187 | BoundingBoxPadding = 2,
188 | Labeler = lType
189 | });
190 | var result = target.Equals(FirstImage, SecondImage);
191 | Assert.IsFalse(result);
192 | }
193 |
194 | [TestCase(AnalyzerTypes.CIE76, BoundingBoxModes.Single, LabelerTypes.Basic)]
195 | [TestCase(AnalyzerTypes.CIE76, BoundingBoxModes.Single, LabelerTypes.ConnectedComponentLabeling)]
196 | [TestCase(AnalyzerTypes.CIE76, BoundingBoxModes.Multiple, LabelerTypes.Basic)]
197 | [TestCase(AnalyzerTypes.CIE76, BoundingBoxModes.Multiple, LabelerTypes.ConnectedComponentLabeling)]
198 | [TestCase(AnalyzerTypes.ExactMatch, BoundingBoxModes.Single, LabelerTypes.Basic)]
199 | [TestCase(AnalyzerTypes.ExactMatch, BoundingBoxModes.Single, LabelerTypes.ConnectedComponentLabeling)]
200 | [TestCase(AnalyzerTypes.ExactMatch, BoundingBoxModes.Multiple, LabelerTypes.Basic)]
201 | [TestCase(AnalyzerTypes.ExactMatch, BoundingBoxModes.Multiple, LabelerTypes.ConnectedComponentLabeling)]
202 | public void EqualsReturnsTrueWithNullImages(AnalyzerTypes aType, BoundingBoxModes bMode, LabelerTypes lType)
203 | {
204 | var target = new BitmapComparer(new CompareOptions
205 | {
206 | BoundingBoxColor = Color.Red,
207 | BoundingBoxMode = bMode,
208 | AnalyzerType = aType,
209 | DetectionPadding = 2,
210 | BoundingBoxPadding = 2,
211 | Labeler = lType
212 | });
213 | var result = target.Equals(null, null);
214 | Assert.IsTrue(result);
215 | }
216 |
217 | [TestCase(AnalyzerTypes.CIE76, BoundingBoxModes.Single, LabelerTypes.Basic)]
218 | [TestCase(AnalyzerTypes.CIE76, BoundingBoxModes.Single, LabelerTypes.ConnectedComponentLabeling)]
219 | [TestCase(AnalyzerTypes.CIE76, BoundingBoxModes.Multiple, LabelerTypes.Basic)]
220 | [TestCase(AnalyzerTypes.CIE76, BoundingBoxModes.Multiple, LabelerTypes.ConnectedComponentLabeling)]
221 | [TestCase(AnalyzerTypes.ExactMatch, BoundingBoxModes.Single, LabelerTypes.Basic)]
222 | [TestCase(AnalyzerTypes.ExactMatch, BoundingBoxModes.Single, LabelerTypes.ConnectedComponentLabeling)]
223 | [TestCase(AnalyzerTypes.ExactMatch, BoundingBoxModes.Multiple, LabelerTypes.Basic)]
224 | [TestCase(AnalyzerTypes.ExactMatch, BoundingBoxModes.Multiple, LabelerTypes.ConnectedComponentLabeling)]
225 | public void EqualsReturnsFalseWithNullFirstImage(AnalyzerTypes aType, BoundingBoxModes bMode, LabelerTypes lType)
226 | {
227 | var target = new BitmapComparer(new CompareOptions
228 | {
229 | BoundingBoxColor = Color.Red,
230 | BoundingBoxMode = bMode,
231 | AnalyzerType = aType,
232 | DetectionPadding = 2,
233 | BoundingBoxPadding = 2,
234 | Labeler = lType
235 | });
236 | var result = target.Equals(null, SecondImage);
237 | Assert.IsFalse(result);
238 | }
239 |
240 | [TestCase(AnalyzerTypes.CIE76, BoundingBoxModes.Single, LabelerTypes.Basic)]
241 | [TestCase(AnalyzerTypes.CIE76, BoundingBoxModes.Single, LabelerTypes.ConnectedComponentLabeling)]
242 | [TestCase(AnalyzerTypes.CIE76, BoundingBoxModes.Multiple, LabelerTypes.Basic)]
243 | [TestCase(AnalyzerTypes.CIE76, BoundingBoxModes.Multiple, LabelerTypes.ConnectedComponentLabeling)]
244 | [TestCase(AnalyzerTypes.ExactMatch, BoundingBoxModes.Single, LabelerTypes.Basic)]
245 | [TestCase(AnalyzerTypes.ExactMatch, BoundingBoxModes.Single, LabelerTypes.ConnectedComponentLabeling)]
246 | [TestCase(AnalyzerTypes.ExactMatch, BoundingBoxModes.Multiple, LabelerTypes.Basic)]
247 | [TestCase(AnalyzerTypes.ExactMatch, BoundingBoxModes.Multiple, LabelerTypes.ConnectedComponentLabeling)]
248 | public void EqualsReturnsFalseWithNullSecondImage(AnalyzerTypes aType, BoundingBoxModes bMode, LabelerTypes lType)
249 | {
250 | var target = new BitmapComparer(new CompareOptions
251 | {
252 | BoundingBoxColor = Color.Red,
253 | BoundingBoxMode = bMode,
254 | AnalyzerType = aType,
255 | DetectionPadding = 2,
256 | BoundingBoxPadding = 2,
257 | Labeler = lType
258 | });
259 | var result = target.Equals(FirstImage, null);
260 | Assert.IsFalse(result);
261 | }
262 | }
263 | }
264 |
--------------------------------------------------------------------------------
/src/ImageDiffTests/BoundingBoxFactoryTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using ImageDiff;
3 | using ImageDiff.BoundingBoxes;
4 | using NUnit.Framework;
5 |
6 | namespace ImageDiffTests
7 | {
8 | [TestFixture]
9 | public class BoundingBoxFactoryTests
10 | {
11 | [Test, ExpectedException(typeof(ArgumentException))]
12 | public void FactoryThrowsWithInvalidType()
13 | {
14 | BoundingBoxIdentifierFactory.Create((BoundingBoxModes)100, 0);
15 | }
16 |
17 | [Test]
18 | public void FactoryCreatesSingleBoundingBoxIdentifier()
19 | {
20 | var target = BoundingBoxIdentifierFactory.Create(BoundingBoxModes.Single, 0);
21 | Assert.IsInstanceOf(typeof(SingleBoundingBoxIdentifer), target);
22 | }
23 |
24 | [Test]
25 | public void FactoryCreatesMultipleBoundingBoxIdentifier()
26 | {
27 | var target = BoundingBoxIdentifierFactory.Create(BoundingBoxModes.Multiple, 0);
28 | Assert.IsInstanceOf(typeof(MultipleBoundingBoxIdentifier), target);
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/ImageDiffTests/ImageDiffTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {0B0B0D1B-490B-4A48-9911-A02930F89FD7}
8 | Library
9 | Properties
10 | ImageDiffTests
11 | ImageDiffTests
12 | v4.5
13 | 512
14 | ..\
15 | true
16 |
17 |
18 | true
19 | full
20 | false
21 | bin\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 |
26 |
27 | pdbonly
28 | true
29 | bin\Release\
30 | TRACE
31 | prompt
32 | 4
33 |
34 |
35 |
36 | False
37 | ..\packages\NUnit.2.6.4\lib\nunit.framework.dll
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | {8c9c4abc-4d3a-4dcd-aee1-95e07e4fae9f}
59 | ImageDiff
60 |
61 |
62 |
63 |
64 | Always
65 |
66 |
67 | Always
68 |
69 |
70 |
71 |
72 |
73 |
74 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
75 |
76 |
77 |
78 |
85 |
--------------------------------------------------------------------------------
/src/ImageDiffTests/LabelerFactoryTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using ImageDiff;
3 | using ImageDiff.Labelers;
4 | using NUnit.Framework;
5 |
6 | namespace ImageDiffTests
7 | {
8 | [TestFixture]
9 | public class LabelerFactoryTests
10 | {
11 | [Test, ExpectedException(typeof(ArgumentException))]
12 | public void FactoryThrowsWithInvalidType()
13 | {
14 | LabelerFactory.Create((LabelerTypes)100, 0);
15 | }
16 |
17 | [Test]
18 | public void FactoryCreatesBasicLabeler()
19 | {
20 | var target = LabelerFactory.Create(LabelerTypes.Basic, 0);
21 | Assert.IsInstanceOf(typeof(BasicLabeler), target);
22 | }
23 |
24 | [Test]
25 | public void FactoryCreatesConnectedComponentLabeler()
26 | {
27 | var target = LabelerFactory.Create(LabelerTypes.ConnectedComponentLabeling, 0);
28 | Assert.IsInstanceOf(typeof(ConnectedComponentLabeler), target);
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/ImageDiffTests/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("ImageDiffTests")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("ImageDiffTests")]
13 | [assembly: AssemblyCopyright("")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("157d2932-0b07-4b90-adaf-f7421c728ae0")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/src/ImageDiffTests/images/TestImage1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/richclement/ImageDiff/953d4a8bf389475f103003a7e455f80885cab8af/src/ImageDiffTests/images/TestImage1.png
--------------------------------------------------------------------------------
/src/ImageDiffTests/images/TestImage2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/richclement/ImageDiff/953d4a8bf389475f103003a7e455f80885cab8af/src/ImageDiffTests/images/TestImage2.png
--------------------------------------------------------------------------------
/src/ImageDiffTests/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------