Skip to content

Commit 3a85a31

Browse files
authored
Allow mask image to be supplied when calculating diff mask of two images
2 parents 1a0e31f + 99edb8e commit 3a85a31

File tree

2 files changed

+217
-1
lines changed

2 files changed

+217
-1
lines changed

ImageSharpCompare/ImageSharpCompare.cs

Lines changed: 131 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
namespace Codeuctivity.ImageSharpCompare
88
{
99
/// <summary>
10-
/// ImageSharpCompare, compares images. Use this class to compare images using a third image as mask of regions where your two compared images may differ. An alpha channel is ignored.
10+
/// ImageSharpCompare, compares images.
11+
/// Use this class to compare images using a third image as mask of regions where your two compared images may differ.
12+
/// An alpha channel is ignored.
1113
/// </summary>
1214
#pragma warning disable CA1724 // Type names should not match namespaces - this is accepted for now to prevent a breaking change
1315

@@ -529,6 +531,22 @@ public static Image CalcDiffMaskImage(string pathActualImage, string pathExpecte
529531
return CalcDiffMaskImage(actual, expected, resizeOption);
530532
}
531533

534+
/// <summary>
535+
/// Creates a diff mask image of two images
536+
/// </summary>
537+
/// <param name="pathActualImage"></param>
538+
/// <param name="pathExpectedImage"></param>
539+
/// <param name="pathMaskImage"></param>
540+
/// <param name="resizeOption"></param>
541+
/// <returns>Image representing diff, black means no diff between actual image and expected image, white means max diff</returns>
542+
public static Image CalcDiffMaskImage(string pathActualImage, string pathExpectedImage, string pathMaskImage, ResizeOption resizeOption = ResizeOption.DontResize)
543+
{
544+
using var actual = Image.Load(pathActualImage);
545+
using var expected = Image.Load(pathExpectedImage);
546+
using var mask = Image.Load(pathMaskImage);
547+
return CalcDiffMaskImage(actual, expected, mask, resizeOption);
548+
}
549+
532550
/// <summary>
533551
/// Creates a diff mask image of two images
534552
/// </summary>
@@ -543,6 +561,22 @@ public static Image CalcDiffMaskImage(Stream actualImage, Stream expectedImage,
543561
return CalcDiffMaskImage(actual, expected, resizeOption);
544562
}
545563

564+
/// <summary>
565+
/// Creates a diff mask image of two images
566+
/// </summary>
567+
/// <param name="actualImage"></param>
568+
/// <param name="expectedImage"></param>
569+
/// <param name="maskImage"></param>
570+
/// <param name="resizeOption"></param>
571+
/// <returns>Image representing diff, black means no diff between actual image and expected image, white means max diff</returns>
572+
public static Image CalcDiffMaskImage(Stream actualImage, Stream expectedImage, Stream maskImage, ResizeOption resizeOption = ResizeOption.DontResize)
573+
{
574+
using var actual = Image.Load(actualImage);
575+
using var expected = Image.Load(expectedImage);
576+
using var mask = Image.Load(maskImage);
577+
return CalcDiffMaskImage(actual, expected, mask, resizeOption);
578+
}
579+
546580
/// <summary>
547581
/// Creates a diff mask image of two images
548582
/// </summary>
@@ -587,6 +621,48 @@ public static Image CalcDiffMaskImage(Image actual, Image expected, ResizeOption
587621
}
588622
}
589623

624+
/// <summary>
625+
/// Creates a diff mask image of two images
626+
/// </summary>
627+
/// <param name="actual"></param>
628+
/// <param name="expected"></param>
629+
/// <param name="mask"></param>
630+
/// <param name="resizeOption"></param>
631+
/// <returns>Image representing diff, black means no diff between actual image and expected image, white means max diff</returns>
632+
public static Image CalcDiffMaskImage(Image actual, Image expected, Image mask, ResizeOption resizeOption = ResizeOption.DontResize)
633+
{
634+
var ownsActual = false;
635+
var ownsExpected = false;
636+
var ownsMask = false;
637+
Image<Rgb24>? actualRgb24 = null;
638+
Image<Rgb24>? expectedRgb24 = null;
639+
Image<Rgb24>? maskRgb24 = null;
640+
641+
try
642+
{
643+
actualRgb24 = ToRgb24Image(actual, out ownsActual);
644+
expectedRgb24 = ToRgb24Image(expected, out ownsExpected);
645+
maskRgb24 = ToRgb24Image(mask, out ownsMask);
646+
647+
return CalcDiffMaskImage(actualRgb24, expectedRgb24, maskRgb24, resizeOption);
648+
}
649+
finally
650+
{
651+
if (ownsActual)
652+
{
653+
actualRgb24?.Dispose();
654+
}
655+
if (ownsExpected)
656+
{
657+
expectedRgb24?.Dispose();
658+
}
659+
if (ownsMask)
660+
{
661+
maskRgb24?.Dispose();
662+
}
663+
}
664+
}
665+
590666
/// <summary>
591667
/// Creates a diff mask image of two images
592668
/// </summary>
@@ -639,6 +715,60 @@ public static Image CalcDiffMaskImage(Image<Rgb24> actual, Image<Rgb24> expected
639715
}
640716
}
641717

718+
/// <summary>
719+
/// Creates a diff mask image of two images using a image mask for tolerated difference between the two images.
720+
/// </summary>
721+
/// <param name="actual"></param>
722+
/// <param name="expected"></param>
723+
/// <param name="mask"></param>
724+
/// <param name="resizeOption"></param>
725+
/// <returns>Image representing diff, black means no diff between actual image and expected image, white means max diff</returns>
726+
public static Image CalcDiffMaskImage(Image<Rgb24> actual, Image<Rgb24> expected, Image<Rgb24> mask, ResizeOption resizeOption = ResizeOption.DontResize)
727+
{
728+
var imagesHaveSameDimensions = ImagesHaveSameDimension(actual, expected) && ImagesHaveSameDimension(actual, mask);
729+
730+
if (!imagesHaveSameDimensions && resizeOption == ResizeOption.DontResize)
731+
{
732+
throw new ImageSharpCompareException(sizeDiffersExceptionMessage);
733+
}
734+
735+
if (imagesHaveSameDimensions)
736+
{
737+
var maskImageResult = new Image<Rgb24>(actual.Width, actual.Height);
738+
739+
for (var x = 0; x < actual.Width; x++)
740+
{
741+
for (var y = 0; y < actual.Height; y++)
742+
{
743+
var maskPixel = mask[x, y];
744+
var actualPixel = actual[x, y];
745+
var expectedPixel = expected[x, y];
746+
747+
maskImageResult[x, y] = new Rgb24
748+
{
749+
R = (byte)Math.Max(byte.MinValue, Math.Abs(expectedPixel.R - actualPixel.R) - maskPixel.R),
750+
G = (byte)Math.Max(byte.MinValue, Math.Abs(expectedPixel.G - actualPixel.G) - maskPixel.G),
751+
B = (byte)Math.Max(byte.MinValue, Math.Abs(expectedPixel.B - actualPixel.B) - maskPixel.B)
752+
};
753+
}
754+
}
755+
756+
return maskImageResult;
757+
}
758+
759+
var grown = GrowToSameDimension(actual, expected, mask);
760+
try
761+
{
762+
return CalcDiffMaskImage(grown.Item1, grown.Item2, grown.Item3, ResizeOption.DontResize);
763+
}
764+
finally
765+
{
766+
grown.Item1?.Dispose();
767+
grown.Item2?.Dispose();
768+
grown.Item3?.Dispose();
769+
}
770+
}
771+
642772
private static Image<Rgb24> ToRgb24Image(Image actual, out bool ownsImage)
643773
{
644774
if (actual is Image<Rgb24> actualPixelAccessibleImage)

ImageSharpCompareTestNunit/ImageSharpCompareTest.cs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Codeuctivity.ImageSharpCompare;
22
using NUnit.Framework;
33
using SixLabors.ImageSharp;
4+
using SixLabors.ImageSharp.PixelFormats;
45
using System;
56
using System.IO;
67
using System.Reflection;
@@ -339,6 +340,70 @@ public void DiffMaskSteams(string pathPic1, string pathPic2, int expectedMeanErr
339340
Assert.That(maskedDiff.PixelErrorPercentage, Is.EqualTo(expectedPixelErrorPercentage), "PixelErrorPercentage");
340341
}
341342

343+
[TestCase(png0Rgba32, png1Rgba32)]
344+
public void CalcDiffMaskImage_WhenSupplyingDiffMaskOfTwoImagesByFilePath_NoDifferences(string image1RelativePath, string image2RelativePath)
345+
{
346+
var image1Path = Path.Combine(AppContext.BaseDirectory, image1RelativePath);
347+
var image2Path = Path.Combine(AppContext.BaseDirectory, image2RelativePath);
348+
var diffMask1Path = Path.GetTempFileName() + "differenceMask.png";
349+
350+
using (var diffMask1Stream = File.Create(diffMask1Path))
351+
{
352+
using var diffMask1Image = ImageSharpCompare.CalcDiffMaskImage(image1Path, image2Path);
353+
ImageExtensions.SaveAsPng(diffMask1Image, diffMask1Stream);
354+
}
355+
356+
using var diffMask2Image = ImageSharpCompare.CalcDiffMaskImage(image1Path, image2Path, diffMask1Path);
357+
Assert.That(IsImageEntirelyBlack(diffMask2Image), Is.True);
358+
359+
File.Delete(diffMask1Path);
360+
}
361+
362+
[TestCase(png0Rgba32, png1Rgba32)]
363+
public void CalcDiffMaskImage_WhenSupplyingDiffMaskOfTwoImagesByStream_NoDifferences(string image1RelativePath, string image2RelativePath)
364+
{
365+
var image1Path = Path.Combine(AppContext.BaseDirectory, image1RelativePath);
366+
var image2Path = Path.Combine(AppContext.BaseDirectory, image2RelativePath);
367+
var diffMask1Path = Path.GetTempFileName() + "differenceMask.png";
368+
369+
using var image1Stream = new FileStream(image1Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
370+
using var image2Stream = new FileStream(image2Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
371+
372+
using (var diffMask1Stream = File.Create(diffMask1Path))
373+
{
374+
using var diffMask1Image = ImageSharpCompare.CalcDiffMaskImage(image1Stream, image2Stream);
375+
ImageExtensions.SaveAsPng(diffMask1Image, diffMask1Stream);
376+
}
377+
378+
image1Stream.Position = 0;
379+
image2Stream.Position = 0;
380+
381+
using (var diffMask1Stream = new FileStream(diffMask1Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
382+
{
383+
diffMask1Stream.Position = 0;
384+
using var diffMask2Image = ImageSharpCompare.CalcDiffMaskImage(image1Stream, image2Stream, diffMask1Stream);
385+
Assert.That(IsImageEntirelyBlack(diffMask2Image), Is.True);
386+
}
387+
388+
File.Delete(diffMask1Path);
389+
}
390+
391+
[TestCase(png0Rgba32, png1Rgba32)]
392+
public void CalcDiffMaskImage_WhenSupplyingDiffMaskOfTwoImagesByImage_NoDifferences(string image1RelativePath, string image2RelativePath)
393+
{
394+
var image1Path = Path.Combine(AppContext.BaseDirectory, image1RelativePath);
395+
var image2Path = Path.Combine(AppContext.BaseDirectory, image2RelativePath);
396+
397+
using var image1 = Image.Load(image1Path);
398+
using var image2 = Image.Load(image2Path);
399+
400+
using var diffMask1Image = ImageSharpCompare.CalcDiffMaskImage(image1, image2);
401+
402+
using var diffMask2Image = ImageSharpCompare.CalcDiffMaskImage(image1, image2, diffMask1Image);
403+
404+
Assert.That(IsImageEntirelyBlack(diffMask2Image), Is.True);
405+
}
406+
342407
[Test]
343408
[TestCase(jpg0Rgb24, jpg1Rgb24)]
344409
[TestCase(png0Rgba32, png1Rgba32)]
@@ -394,5 +459,26 @@ public void ShouldVerifyThatImageWithDifferentSizeThrows(string pathPic1, string
394459

395460
Assert.That(exception?.Message, Is.EqualTo("Size of images differ."));
396461
}
462+
463+
private static bool IsImageEntirelyBlack(Image image)
464+
{
465+
if (!(image is Image<Rgb24> imageRgb24))
466+
{
467+
throw new ArgumentException("Image must be an RGB 24 one", nameof(image));
468+
}
469+
470+
for (var x = 0; x < imageRgb24.Width; x++)
471+
{
472+
for (var y = 0; y < imageRgb24.Height; y++)
473+
{
474+
if (imageRgb24[x, y] != new Rgb24(byte.MinValue, byte.MinValue, byte.MinValue))
475+
{
476+
return false;
477+
}
478+
}
479+
}
480+
481+
return true;
482+
}
397483
}
398484
}

0 commit comments

Comments
 (0)