From bef2e817a7b747c675f4226beb06b51f1521a0d2 Mon Sep 17 00:00:00 2001 From: Stefan Seeland <168659+stesee@users.noreply.github.com> Date: Tue, 7 Jan 2025 09:18:18 +0100 Subject: [PATCH 01/19] Update SkiaSharpCompareTests.cs --- SkiaSharpCompareTestNunit/SkiaSharpCompareTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SkiaSharpCompareTestNunit/SkiaSharpCompareTests.cs b/SkiaSharpCompareTestNunit/SkiaSharpCompareTests.cs index 36e4513..b5efcf5 100644 --- a/SkiaSharpCompareTestNunit/SkiaSharpCompareTests.cs +++ b/SkiaSharpCompareTestNunit/SkiaSharpCompareTests.cs @@ -358,7 +358,7 @@ public void DiffMaskStreams(string pathPic1, string pathPic2, int expectedMeanEr [TestCase(colorShift1, colorShift2, TransparencyOptions.IgnoreAlphaChannel, 20)] [TestCase(pngTransparent2x2px, pngPartialTransparent2x2px, TransparencyOptions.IgnoreAlphaChannel, 0)] [TestCase(pngTransparent2x2px, pngPartialTransparent2x2px, TransparencyOptions.CompareAlphaChannel, 0)] - public void CalcDiffMaskImage_WhenSupplyingDiffMaskOfTwoImagesByFilePath_NoDifferences(string image1RelativePath, string image2RelativePath, int pixelColorShiftTolerance, TransparencyOptions transparencyOptions) + public void CalcDiffMaskImage_WhenSupplyingDiffMaskOfTwoImagesByFilePath_NoDifferences(string image1RelativePath, string image2RelativePath, TransparencyOptions transparencyOptions, int pixelColorShiftTolerance) { var sut = new ImageCompare(ResizeOption.DontResize, transparencyOptions, pixelColorShiftTolerance); var image1Path = Path.Combine(AppContext.BaseDirectory, image1RelativePath); From d9ebd8b087a0501f15e2c1d75930e7c32b697fc5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Apr 2025 05:40:17 +0000 Subject: [PATCH 02/19] Bump SkiaSharp.NativeAssets.Linux.NoDependencies from 3.116.1 to 3.119.0 Bumps SkiaSharp.NativeAssets.Linux.NoDependencies from 3.116.1 to 3.119.0. --- updated-dependencies: - dependency-name: SkiaSharp.NativeAssets.Linux.NoDependencies dependency-version: 3.119.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- SkiaSharpCompare/SkiaSharpCompare.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SkiaSharpCompare/SkiaSharpCompare.csproj b/SkiaSharpCompare/SkiaSharpCompare.csproj index fe51edc..6d16408 100644 --- a/SkiaSharpCompare/SkiaSharpCompare.csproj +++ b/SkiaSharpCompare/SkiaSharpCompare.csproj @@ -1,4 +1,4 @@ - + net8.0 true @@ -47,7 +47,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 4ce3e470b1835e6ba39c4fac850c2fc0310a30ba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Apr 2025 05:40:39 +0000 Subject: [PATCH 03/19] Bump SkiaSharp from 3.116.1 to 3.119.0 Bumps SkiaSharp from 3.116.1 to 3.119.0. --- updated-dependencies: - dependency-name: SkiaSharp dependency-version: 3.119.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- SkiaSharpCompare/SkiaSharpCompare.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SkiaSharpCompare/SkiaSharpCompare.csproj b/SkiaSharpCompare/SkiaSharpCompare.csproj index fe51edc..e926e13 100644 --- a/SkiaSharpCompare/SkiaSharpCompare.csproj +++ b/SkiaSharpCompare/SkiaSharpCompare.csproj @@ -1,4 +1,4 @@ - + net8.0 true @@ -46,7 +46,7 @@ - + all From 3e60a8495eaf2042a0847fb1209cc81cf9167d32 Mon Sep 17 00:00:00 2001 From: "Stefan.Seeland" <168659+stesee@users.noreply.github.com> Date: Wed, 30 Apr 2025 21:58:52 +0200 Subject: [PATCH 04/19] Fixed ignored parameter 'pixelColorShiftTolerance' in ImagesAreEqual and CalcDiffMaskImage Replaced obsolate usages of SKFilterQuality.None -> SKSamplingOptions.Default Fixed ignored parameter 'pixelColorShiftTolerance' in ImagesAreEqual and CalcDiffMaskImage --- SkiaSharpCompare/SkiaSharpCompare.cs | 69 ++++++++++++++++--- SkiaSharpCompare/SkiaSharpCompare.csproj | 4 +- .../SkiaSharpCompareTestNunit.cs | 44 +++++++++--- .../SkiaSharpCompareTestNunit.csproj | 12 ++-- 4 files changed, 102 insertions(+), 27 deletions(-) diff --git a/SkiaSharpCompare/SkiaSharpCompare.cs b/SkiaSharpCompare/SkiaSharpCompare.cs index a39d842..e257b11 100644 --- a/SkiaSharpCompare/SkiaSharpCompare.cs +++ b/SkiaSharpCompare/SkiaSharpCompare.cs @@ -102,10 +102,25 @@ public static bool ImagesAreEqual(SKBitmap actual, SKBitmap expected, ResizeOpti { for (var y = 0; y < actual.Height; y++) { - if (!actual.GetPixel(x, y).Equals(expected.GetPixel(x, y))) + if (pixelColorShiftTolerance == 0 && !actual.GetPixel(x, y).Equals(expected.GetPixel(x, y))) { return false; } + else + { + var actualPixel = actual.GetPixel(x, y); + var expectedPixel = expected.GetPixel(x, y); + + var r = Math.Abs(expectedPixel.Red - actualPixel.Red); + var g = Math.Abs(expectedPixel.Green - actualPixel.Green); + var b = Math.Abs(expectedPixel.Blue - actualPixel.Blue); + var sum = r + g + b; + + if (sum > pixelColorShiftTolerance) + { + return false; + } + } } } @@ -472,7 +487,26 @@ public static SKBitmap CalcDiffMaskImage(SKBitmap actual, SKBitmap expected, Res var blue = (byte)Math.Abs(actualPixel.Blue - expectedPixel.Blue); var pixel = new SKColor(red, green, blue); - maskImage.SetPixel(x, y, pixel); + if (pixelColorShiftTolerance == 0) + { + maskImage.SetPixel(x, y, pixel); + } + else + { + var r = Math.Abs(expectedPixel.Red - actualPixel.Red); + var g = Math.Abs(expectedPixel.Green - actualPixel.Green); + var b = Math.Abs(expectedPixel.Blue - actualPixel.Blue); + var sum = r + g + b; + + if (sum > pixelColorShiftTolerance) + { + maskImage.SetPixel(x, y, pixel); + } + else + { + maskImage.SetPixel(x, y, 0); + } + } } } return maskImage; @@ -527,7 +561,26 @@ public static SKBitmap CalcDiffMaskImage(SKBitmap actual, SKBitmap expected, SKB var blue = (byte)(Math.Abs(actualPixel.Blue - expectedPixel.Blue) - maskPixel.Blue); var pixel = new SKColor(red, green, blue); - maskImage.SetPixel(x, y, pixel); + if (pixelColorShiftTolerance == 0) + { + maskImage.SetPixel(x, y, pixel); + } + else + { + var r = Math.Abs(expectedPixel.Red - actualPixel.Red); + var g = Math.Abs(expectedPixel.Green - actualPixel.Green); + var b = Math.Abs(expectedPixel.Blue - actualPixel.Blue); + var sum = r + g + b; + + if (sum > pixelColorShiftTolerance) + { + maskImage.SetPixel(x, y, pixel); + } + else + { + maskImage.SetPixel(x, y, 0); + } + } } } return maskImage; @@ -551,8 +604,8 @@ private static (SKBitmap, SKBitmap) GrowToSameDimension(SKBitmap actual, SKBitma var biggestWidth = actual.Width > expected.Width ? actual.Width : expected.Width; var biggestHeight = actual.Height > expected.Height ? actual.Height : expected.Height; var skSize = new SKSizeI(biggestWidth, biggestHeight); - var grownExpected = expected.Resize(skSize, SKFilterQuality.None); - var grownActual = actual.Resize(skSize, SKFilterQuality.None); + var grownExpected = expected.Resize(skSize, SKSamplingOptions.Default); + var grownActual = actual.Resize(skSize, SKSamplingOptions.Default); return (grownActual, grownExpected); } @@ -564,9 +617,9 @@ private static (SKBitmap, SKBitmap, SKBitmap) GrowToSameDimension(SKBitmap actua var biggestHeight = actual.Height > expected.Height ? actual.Height : expected.Height; biggestHeight = biggestHeight > mask.Height ? biggestHeight : mask.Height; var skSize = new SKSizeI(biggestWidth, biggestHeight); - var grownExpected = expected.Resize(skSize, SKFilterQuality.None); - var grownActual = actual.Resize(skSize, SKFilterQuality.None); - var grownMask = mask.Resize(skSize, SKFilterQuality.None); + var grownExpected = expected.Resize(skSize, SKSamplingOptions.Default); + var grownActual = actual.Resize(skSize, SKSamplingOptions.Default); + var grownMask = mask.Resize(skSize, SKSamplingOptions.Default); return (grownActual, grownExpected, grownMask); } diff --git a/SkiaSharpCompare/SkiaSharpCompare.csproj b/SkiaSharpCompare/SkiaSharpCompare.csproj index e8d5e26..109f276 100644 --- a/SkiaSharpCompare/SkiaSharpCompare.csproj +++ b/SkiaSharpCompare/SkiaSharpCompare.csproj @@ -1,6 +1,6 @@ - net8.0 + net8.0;net9.0 true true https://github.com/Codeuctivity/SkiaSharp.Compare @@ -48,7 +48,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/SkiaSharpCompareTestNunit/SkiaSharpCompareTestNunit.cs b/SkiaSharpCompareTestNunit/SkiaSharpCompareTestNunit.cs index 1db3079..a9ad442 100644 --- a/SkiaSharpCompareTestNunit/SkiaSharpCompareTestNunit.cs +++ b/SkiaSharpCompareTestNunit/SkiaSharpCompareTestNunit.cs @@ -70,14 +70,15 @@ public void ShouldVerifyThatImageStreamsSizeAreEqual(string pathActual, string p } [Test] - [TestCase(jpg0Rgb24, jpg0Rgb24)] - [TestCase(png0Rgba32, png0Rgba32)] - public void ShouldVerifyThatImagesAreEqual(string pathActual, string pathExpected) + [TestCase(jpg0Rgb24, jpg0Rgb24, 0)] + [TestCase(png0Rgba32, png0Rgba32, 0)] + [TestCase(colorShift1, colorShift2, 15)] + public void ShouldVerifyThatImagesAreEqual(string pathActual, string pathExpected, int pixelColorShiftTolerance) { var absolutePathActual = Path.Combine(AppContext.BaseDirectory, pathActual); var absolutePathExpected = Path.Combine(AppContext.BaseDirectory, pathExpected); - Assert.That(Compare.ImagesAreEqual(absolutePathActual, absolutePathExpected), Is.True); + Assert.That(Compare.ImagesAreEqual(absolutePathActual, absolutePathExpected, pixelColorShiftTolerance: pixelColorShiftTolerance), Is.True); } [Test] @@ -318,9 +319,10 @@ public void DiffMaskStreams(string pathPic1, string pathPic2, int expectedMeanEr Assert.That(maskedDiff.PixelErrorPercentage, Is.EqualTo(expectedPixelErrorPercentage), "PixelErrorPercentage"); } - [TestCase(png0Rgba32, png1Rgba32, 0)] - [TestCase(colorShift1, colorShift2, 20)] - public void CalcDiffMaskImage_WhenSupplyingDiffMaskOfTwoImagesByFilePath_NoDifferences(string image1RelativePath, string image2RelativePath, int pixelColorShiftTolerance) + [TestCase(png0Rgba32, png1Rgba32, 0, true)] + [TestCase(colorShift1, colorShift2, 20, false)] + [TestCase(colorShift1, colorShift2, 0, true)] + public void CalcDiffMaskImage_WhenSupplyingDiffMaskOfTwoImagesByFilePath_NoDifferences(string image1RelativePath, string image2RelativePath, int pixelColorShiftTolerance, bool expectIsImageEntirelyBlack) { var image1Path = Path.Combine(AppContext.BaseDirectory, image1RelativePath); var image2Path = Path.Combine(AppContext.BaseDirectory, image2RelativePath); @@ -339,7 +341,7 @@ public void CalcDiffMaskImage_WhenSupplyingDiffMaskOfTwoImagesByFilePath_NoDiffe ImageExtensions.SaveAsPng(diffMask2Image, diffMask2Stream); } - Assert.That(IsImageEntirelyBlack(diffMask2Image), Is.True); + Assert.That(IsImageEntirelyBlack(diffMask2Image), Is.EqualTo(expectIsImageEntirelyBlack)); File.Delete(diffMask1Path); } @@ -373,22 +375,42 @@ public void CalcDiffMaskImage_WhenSupplyingDiffMaskOfTwoImagesByStream_NoDiffere File.Delete(diffMask1Path); } - [TestCase(png0Rgba32, png1Rgba32)] - public void CalcDiffMaskImage_WhenSupplyingDiffMaskOfTwoImagesByImage_NoDifferences(string image1RelativePath, string image2RelativePath) + [TestCase(png0Rgba32, png1Rgba32, png1Rgba32,0, false)] + [TestCase(colorShift1, colorShift1, colorShift2, 15, true)] + public void CalcDiffMaskImage_WhenSupplyingDiffMaskOfTwoImagesByImage_NoDifferences(string image1RelativePath, string image2RelativePath, string image3RelativePath, int expectedPixelColorShiftTolerance, bool expectToleranceMaskToEntirelyBlack ) { var image1Path = Path.Combine(AppContext.BaseDirectory, image1RelativePath); var image2Path = Path.Combine(AppContext.BaseDirectory, image2RelativePath); + var image3Path = Path.Combine(AppContext.BaseDirectory, image3RelativePath); using var image1 = SKBitmap.Decode(image1Path); using var image2 = SKBitmap.Decode(image2Path); + using var image3 = SKBitmap.Decode(image3Path); using var diffMask1Image = Compare.CalcDiffMaskImage(image1, image2); - using var diffMask2Image = Compare.CalcDiffMaskImage(image1, image2, diffMask1Image); + using var diffMask2Image = Compare.CalcDiffMaskImage(image1, image3, diffMask1Image, pixelColorShiftTolerance: expectedPixelColorShiftTolerance); + Assert.That(IsImageEntirelyBlack(diffMask1Image), Is.EqualTo(expectToleranceMaskToEntirelyBlack)); Assert.That(IsImageEntirelyBlack(diffMask2Image), Is.True); } + [TestCase(png0Rgba32, png0Rgba32, 0)] + [TestCase(png0Rgba32, png0Rgba32, 15)] + [TestCase(colorShift1, colorShift2, 15)] + public void CalcDiffMaskImage_ToleranceColorShift_NoDifferences(string image1RelativePath, string image2RelativePath, int expectedPixelColorShiftTolerance) + { + var image1Path = Path.Combine(AppContext.BaseDirectory, image1RelativePath); + var image2Path = Path.Combine(AppContext.BaseDirectory, image2RelativePath); + + using var image1 = SKBitmap.Decode(image1Path); + using var image2 = SKBitmap.Decode(image2Path); + + using var diffMask1Image = Compare.CalcDiffMaskImage(image1, image2, pixelColorShiftTolerance: expectedPixelColorShiftTolerance); + + Assert.That(IsImageEntirelyBlack(diffMask1Image), Is.True); + } + [Test] [TestCase(jpg0Rgb24, jpg1Rgb24)] [TestCase(png0Rgba32, png1Rgba32)] diff --git a/SkiaSharpCompareTestNunit/SkiaSharpCompareTestNunit.csproj b/SkiaSharpCompareTestNunit/SkiaSharpCompareTestNunit.csproj index 69ad7a3..4b44478 100644 --- a/SkiaSharpCompareTestNunit/SkiaSharpCompareTestNunit.csproj +++ b/SkiaSharpCompareTestNunit/SkiaSharpCompareTestNunit.csproj @@ -1,27 +1,27 @@  - net8.0 + net8.0;net9.0 false enable true - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + From 004843c2c790879c716a800d1fbcc6178bef69d8 Mon Sep 17 00:00:00 2001 From: "Stefan.Seeland" <168659+stesee@users.noreply.github.com> Date: Wed, 30 Apr 2025 22:02:32 +0200 Subject: [PATCH 05/19] Incread minor version --- .github/FUNDING.yml | 15 +++++++++++++++ .github/workflows/dotnet.yml | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..36ace77 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,15 @@ +# These are supported funding model platforms + +github: [Codeuctivity] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +polar: # Replace with a single Polar username +buy_me_a_coffee: # Replace with a single Buy Me a Coffee username +thanks_dev: # Replace with a single thanks.dev username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 0c4bbdd..aa19e95 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -1,6 +1,6 @@ name: .NET build and test env: - CURRENT_VERSION: 2.0.${{ github.run_number }} + CURRENT_VERSION: 2.1.${{ github.run_number }} LAST_COMMIT_MESSAGE: ${{ github.event.head_commit.message }} on: From 73f7abc3a48b4ec9c91718cf30c47615be089817 Mon Sep 17 00:00:00 2001 From: "Stefan.Seeland" <168659+stesee@users.noreply.github.com> Date: Wed, 30 Apr 2025 22:05:30 +0200 Subject: [PATCH 06/19] Code formated --- .github/workflows/dotnet.yml | 3 +++ SkiaSharpCompareTestNunit/SkiaSharpCompareTestNunit.cs | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index aa19e95..ab27311 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -20,6 +20,7 @@ jobs: with: dotnet-version: | 8.0.x + 9.0.x - name: Check formatting run: dotnet format --verify-no-changes - name: Restore dependencies @@ -40,6 +41,7 @@ jobs: with: dotnet-version: | 8.0.x + 9.0.x - name: Restore dependencies run: dotnet restore - name: Build @@ -69,6 +71,7 @@ jobs: with: dotnet-version: | 8.0.x + 9.0.x - name: Restore dependencies run: dotnet restore - name: Build diff --git a/SkiaSharpCompareTestNunit/SkiaSharpCompareTestNunit.cs b/SkiaSharpCompareTestNunit/SkiaSharpCompareTestNunit.cs index a9ad442..c410f23 100644 --- a/SkiaSharpCompareTestNunit/SkiaSharpCompareTestNunit.cs +++ b/SkiaSharpCompareTestNunit/SkiaSharpCompareTestNunit.cs @@ -375,9 +375,9 @@ public void CalcDiffMaskImage_WhenSupplyingDiffMaskOfTwoImagesByStream_NoDiffere File.Delete(diffMask1Path); } - [TestCase(png0Rgba32, png1Rgba32, png1Rgba32,0, false)] + [TestCase(png0Rgba32, png1Rgba32, png1Rgba32, 0, false)] [TestCase(colorShift1, colorShift1, colorShift2, 15, true)] - public void CalcDiffMaskImage_WhenSupplyingDiffMaskOfTwoImagesByImage_NoDifferences(string image1RelativePath, string image2RelativePath, string image3RelativePath, int expectedPixelColorShiftTolerance, bool expectToleranceMaskToEntirelyBlack ) + public void CalcDiffMaskImage_WhenSupplyingDiffMaskOfTwoImagesByImage_NoDifferences(string image1RelativePath, string image2RelativePath, string image3RelativePath, int expectedPixelColorShiftTolerance, bool expectToleranceMaskToEntirelyBlack) { var image1Path = Path.Combine(AppContext.BaseDirectory, image1RelativePath); var image2Path = Path.Combine(AppContext.BaseDirectory, image2RelativePath); From b1947d79eede2cb92649b3733ddff51897f2e5c1 Mon Sep 17 00:00:00 2001 From: "Stefan.Seeland" <168659+stesee@users.noreply.github.com> Date: Wed, 30 Apr 2025 22:06:49 +0200 Subject: [PATCH 07/19] Update SkiaSharpCompareTestNunit.cs --- SkiaSharpCompareTestNunit/SkiaSharpCompareTestNunit.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SkiaSharpCompareTestNunit/SkiaSharpCompareTestNunit.cs b/SkiaSharpCompareTestNunit/SkiaSharpCompareTestNunit.cs index c410f23..321431a 100644 --- a/SkiaSharpCompareTestNunit/SkiaSharpCompareTestNunit.cs +++ b/SkiaSharpCompareTestNunit/SkiaSharpCompareTestNunit.cs @@ -210,7 +210,7 @@ public void Diffmask(string pathPic1, string pathPic2, int expectedMeanError, in using (var fileStreamDifferenceMask = File.Create(differenceMask)) using (var maskImage = Compare.CalcDiffMaskImage(absolutePathPic1, absolutePathPic2, resizeOption)) { - SaveAsPng(maskImage, fileStreamDifferenceMask); + IntegrationTest.SaveAsPng(maskImage, fileStreamDifferenceMask); } var maskedDiff = Compare.CalcDiff(absolutePathPic1, absolutePathPic2, differenceMask, resizeOption); @@ -222,7 +222,7 @@ public void Diffmask(string pathPic1, string pathPic2, int expectedMeanError, in Assert.That(maskedDiff.PixelErrorPercentage, Is.EqualTo(expectedPixelErrorPercentage), "PixelErrorPercentage"); } - private void SaveAsPng(SKBitmap maskImage, FileStream fileStreamDifferenceMask) + private static void SaveAsPng(SKBitmap maskImage, FileStream fileStreamDifferenceMask) { var encodedData = maskImage.Encode(SKEncodedImageFormat.Png, 100); encodedData.SaveTo(fileStreamDifferenceMask); @@ -244,7 +244,7 @@ public void ShouldCalcDiffMaskSKBitmapAndUseOutcome(string pathPic1, string path using (var fileStreamDifferenceMask = File.Create(differenceMaskPicPath)) using (var maskImage = Compare.CalcDiffMaskImage(absolutePic1, absolutePic2, resizeOption)) { - SaveAsPng(maskImage, fileStreamDifferenceMask); + IntegrationTest.SaveAsPng(maskImage, fileStreamDifferenceMask); } using var differenceMaskPic = SKBitmap.Decode(differenceMaskPicPath); From 28594de679b872bedbf270929e8f9d60d8c1f592 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Aug 2025 07:38:04 +0000 Subject: [PATCH 08/19] Bump actions/checkout from 4 to 5 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/dotnet.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index ab27311..acd1994 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -14,7 +14,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup .NET uses: actions/setup-dotnet@v4 with: @@ -35,7 +35,7 @@ jobs: runs-on: ubuntu-latest needs: build steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup .NET uses: actions/setup-dotnet@v4 with: @@ -65,7 +65,7 @@ jobs: runs-on: ubuntu-latest needs: build steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup .NET uses: actions/setup-dotnet@v4 with: From fbbc5b0a11b059c57c6aa44e44d9442a3a0c9037 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 05:04:24 +0000 Subject: [PATCH 09/19] Bump actions/stale from 9 to 10 Bumps [actions/stale](https://github.com/actions/stale) from 9 to 10. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/v9...v10) --- updated-dependencies: - dependency-name: actions/stale dependency-version: '10' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index d82d991..6736c09 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -18,7 +18,7 @@ jobs: pull-requests: write steps: - - uses: actions/stale@v9 + - uses: actions/stale@v10 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: 'Stale issue message' From 6ded36514315cb10acf756bd681c8b093f211d86 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 05:04:27 +0000 Subject: [PATCH 10/19] Bump actions/setup-dotnet from 4 to 5 Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 4 to 5. - [Release notes](https://github.com/actions/setup-dotnet/releases) - [Commits](https://github.com/actions/setup-dotnet/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-dotnet dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/dotnet.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index ab27311..ef8c9de 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: dotnet-version: | 8.0.x @@ -37,7 +37,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: dotnet-version: | 8.0.x @@ -67,7 +67,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: dotnet-version: | 8.0.x From eeb2063c57da28c3d08400a751b43a7b24645cbf Mon Sep 17 00:00:00 2001 From: Stefan Seeland <168659+stesee@users.noreply.github.com> Date: Fri, 26 Sep 2025 00:09:21 +0200 Subject: [PATCH 11/19] Update workflow and project files: remove pull_request trigger, add NuGet update script, and update package versions --- .github/workflows/dotnet.yml | 1 - .vscode/tasks.json | 12 ++++++++++ .vscode/updateNuget.ps1 | 23 +++++++++++++++++++ SkiaSharpCompare/SkiaSharpCompare.csproj | 9 ++++---- SkiaSharpCompare/docs/nugetReadme.md | 1 - .../SkiaSharpCompareTestNunit.csproj | 8 +++---- 6 files changed, 43 insertions(+), 11 deletions(-) create mode 100644 .vscode/updateNuget.ps1 diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index ab27311..6881a1d 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -5,7 +5,6 @@ env: on: push: - pull_request: jobs: build: diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 452e451..c22253a 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -43,6 +43,18 @@ "type": "shell", "command": "'./.vscode/updateNuget.sh'", "problemMatcher": [] + }, + { + "label": "update nuget packages (windows)", + "type": "shell", + "command": "powershell", + "args": [ + "-ExecutionPolicy", + "Bypass", + "-File", + "./.vscode/updateNuget.ps1" + ], + "problemMatcher": [] } ] } \ No newline at end of file diff --git a/.vscode/updateNuget.ps1 b/.vscode/updateNuget.ps1 new file mode 100644 index 0000000..8977003 --- /dev/null +++ b/.vscode/updateNuget.ps1 @@ -0,0 +1,23 @@ +# PowerShell script to update NuGet packages in all project files +# This script finds all .csproj and .fsproj files and updates stable (non-prerelease) packages + +$regex = 'PackageReference Include="([^"]*)" Version="([^"]*)"' + +Get-ChildItem -Path . -Recurse -Include "*.csproj", "*.fsproj" | ForEach-Object { + $projFile = $_.FullName + Write-Host "Processing project: $projFile" + $content = Get-Content $projFile + foreach ($line in $content) { + if ($line -match $regex) { + $packageName = $matches[1] + $version = $matches[2] + Write-Host "Found package: $packageName, version: $version" + + # Only update stable versions (not prerelease versions containing -) + if ($version -notmatch '-') { + Write-Host "Updating package: $packageName" + dotnet add "$projFile" package "$packageName" + } + } + } +} \ No newline at end of file diff --git a/SkiaSharpCompare/SkiaSharpCompare.csproj b/SkiaSharpCompare/SkiaSharpCompare.csproj index 109f276..fd43e02 100644 --- a/SkiaSharpCompare/SkiaSharpCompare.csproj +++ b/SkiaSharpCompare/SkiaSharpCompare.csproj @@ -4,7 +4,7 @@ true true https://github.com/Codeuctivity/SkiaSharp.Compare - Compare Image JPG PNG BMP Linux Windows MacOs + Compare Image JPG PNG BMP Stefan Seeland Codeuctivity $(CURRENT_VERSION) @@ -45,10 +45,9 @@ - - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/SkiaSharpCompare/docs/nugetReadme.md b/SkiaSharpCompare/docs/nugetReadme.md index ac77b9b..89ac9a7 100644 --- a/SkiaSharpCompare/docs/nugetReadme.md +++ b/SkiaSharpCompare/docs/nugetReadme.md @@ -24,4 +24,3 @@ Console.WriteLine($"MeanError: {diff.MeanError}"); - Diff mask: allowing defined areas to diff from the compared image. - Compare images that have different dimension -- Linux, MacOs and Windows supported diff --git a/SkiaSharpCompareTestNunit/SkiaSharpCompareTestNunit.csproj b/SkiaSharpCompareTestNunit/SkiaSharpCompareTestNunit.csproj index 4b44478..7b5a91d 100644 --- a/SkiaSharpCompareTestNunit/SkiaSharpCompareTestNunit.csproj +++ b/SkiaSharpCompareTestNunit/SkiaSharpCompareTestNunit.csproj @@ -12,16 +12,16 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + From f99301d128b687eca21cbe40177a4579ae948f6d Mon Sep 17 00:00:00 2001 From: Stefan Seeland <168659+stesee@users.noreply.github.com> Date: Sat, 27 Sep 2025 14:37:04 +0200 Subject: [PATCH 12/19] Update .NET version in workflow configuration --- .github/workflows/dotnet.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 183e012..d600376 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -1,6 +1,6 @@ name: .NET build and test env: - CURRENT_VERSION: 2.1.${{ github.run_number }} + CURRENT_VERSION: 3.1.${{ github.run_number }} LAST_COMMIT_MESSAGE: ${{ github.event.head_commit.message }} on: From 13f6a17961c3b30e385933f66289fed2ed07556b Mon Sep 17 00:00:00 2001 From: Stefan Seeland <168659+stesee@users.noreply.github.com> Date: Wed, 1 Oct 2025 19:26:38 +0200 Subject: [PATCH 13/19] Improve diff mask calculation and test naming Refactored diff mask logic to handle pixelColorShiftTolerance and transparency options more accurately in SkiaSharpCompare.cs. Updated test methods and variable names for clarity and consistency in SkiaSharpCompareTests.cs and SkiaSharpStaticCompareTests.cs. --- SkiaSharpCompare/SkiaSharpCompare.cs | 125 +++++++++++++++--- .../SkiaSharpCompareTests.cs | 14 +- .../SkiaSharpStaticCompareTests.cs | 6 +- 3 files changed, 115 insertions(+), 30 deletions(-) diff --git a/SkiaSharpCompare/SkiaSharpCompare.cs b/SkiaSharpCompare/SkiaSharpCompare.cs index af32747..24a435a 100644 --- a/SkiaSharpCompare/SkiaSharpCompare.cs +++ b/SkiaSharpCompare/SkiaSharpCompare.cs @@ -105,16 +105,7 @@ public static bool ImagesAreEqual(SKBitmap actual, SKBitmap expected, ResizeOpti { for (var y = 0; y < actual.Height; y++) { - if (transparencyOptions == TransparencyOptions.IgnoreAlphaChannel) - { - var actualPixel = actual.GetPixel(x, y); - var expectedPixel = expected.GetPixel(x, y); - if (actualPixel.Red != expectedPixel.Red || actualPixel.Green != expectedPixel.Green || actualPixel.Blue != expectedPixel.Blue) - { - return false; - } - } - else if (!actual.GetPixel(x, y).Equals(expected.GetPixel(x, y))) + if (transparencyOptions == TransparencyOptions.CompareAlphaChannel && pixelColorShiftTolerance == 0 && !actual.GetPixel(x, y).Equals(expected.GetPixel(x, y))) { return false; } @@ -122,11 +113,17 @@ public static bool ImagesAreEqual(SKBitmap actual, SKBitmap expected, ResizeOpti { var actualPixel = actual.GetPixel(x, y); var expectedPixel = expected.GetPixel(x, y); + var a = 0; + + if (transparencyOptions == TransparencyOptions.CompareAlphaChannel) + { + a = Math.Abs(expectedPixel.Alpha - actualPixel.Alpha); + } var r = Math.Abs(expectedPixel.Red - actualPixel.Red); var g = Math.Abs(expectedPixel.Green - actualPixel.Green); var b = Math.Abs(expectedPixel.Blue - actualPixel.Blue); - var sum = r + g + b; + var sum = r + g + b + a; if (sum > pixelColorShiftTolerance) { @@ -525,17 +522,58 @@ public static SKBitmap CalcDiffMaskImage(SKBitmap actual, SKBitmap expected, Res var green = (byte)Math.Abs(actualPixel.Green - expectedPixel.Green); var blue = (byte)Math.Abs(actualPixel.Blue - expectedPixel.Blue); - if (transparencyOptions == TransparencyOptions.CompareAlphaChannel) + if (pixelColorShiftTolerance == 0) { - var alpha = (byte)Math.Abs(actualPixel.Alpha - expectedPixel.Alpha); - var pixel = new SKColor(red, green, blue); - pixel.WithAlpha(alpha); - maskImage.SetPixel(x, y, pixel); + if (transparencyOptions == TransparencyOptions.CompareAlphaChannel) + { + var alpha = (byte)Math.Abs(actualPixel.Alpha - expectedPixel.Alpha); + var pixel = new SKColor(red, green, blue, alpha); + maskImage.SetPixel(x, y, pixel); + } + else + { + var pixel = new SKColor(red, green, blue); + maskImage.SetPixel(x, y, pixel); + } } else { - var pixel = new SKColor(red, green, blue); - maskImage.SetPixel(x, y, pixel); + if (transparencyOptions == TransparencyOptions.CompareAlphaChannel) + { + var alpha = (byte)Math.Abs(actualPixel.Alpha - expectedPixel.Alpha); + var pixel = new SKColor(red, green, blue, alpha); + var r = Math.Abs(expectedPixel.Red - actualPixel.Red); + var g = Math.Abs(expectedPixel.Green - actualPixel.Green); + var b = Math.Abs(expectedPixel.Blue - actualPixel.Blue); + var a = Math.Abs(expectedPixel.Alpha - actualPixel.Alpha); + var sum = r + g + b + a; + + if (sum > pixelColorShiftTolerance) + { + maskImage.SetPixel(x, y, pixel); + } + else + { + maskImage.SetPixel(x, y, 0); + } + } + else + { + var pixel = new SKColor(red, green, blue); + var r = Math.Abs(expectedPixel.Red - actualPixel.Red); + var g = Math.Abs(expectedPixel.Green - actualPixel.Green); + var b = Math.Abs(expectedPixel.Blue - actualPixel.Blue); + var sum = r + g + b; + + if (sum > pixelColorShiftTolerance) + { + maskImage.SetPixel(x, y, pixel); + } + else + { + maskImage.SetPixel(x, y, 0); + } + } } } } @@ -590,12 +628,59 @@ public static SKBitmap CalcDiffMaskImage(SKBitmap actual, SKBitmap expected, SKB var red = (byte)(Math.Abs(actualPixel.Red - expectedPixel.Red) - maskPixel.Red); var green = (byte)(Math.Abs(actualPixel.Green - expectedPixel.Green) - maskPixel.Green); var blue = (byte)(Math.Abs(actualPixel.Blue - expectedPixel.Blue) - maskPixel.Blue); + if (transparencyOptions == TransparencyOptions.CompareAlphaChannel) { var alpha = (byte)(Math.Abs(actualPixel.Alpha - expectedPixel.Alpha) - maskPixel.Alpha); - maskImage.SetPixel(x, y, new SKColor(red, green, blue, alpha)); + var pixel = new SKColor(red, green, blue, alpha); + + if (pixelColorShiftTolerance == 0) + { + maskImage.SetPixel(x, y, pixel); + } + else + { + var r = Math.Abs(expectedPixel.Red - actualPixel.Red); + var g = Math.Abs(expectedPixel.Green - actualPixel.Green); + var b = Math.Abs(expectedPixel.Blue - actualPixel.Blue); + var a = Math.Abs(expectedPixel.Alpha - actualPixel.Alpha); + var sum = r + g + b + a; + + if (sum > pixelColorShiftTolerance) + { + maskImage.SetPixel(x, y, pixel); + } + else + { + maskImage.SetPixel(x, y, 0); + } + } + } + else + { + var pixel = new SKColor(red, green, blue); + + if (pixelColorShiftTolerance == 0) + { + maskImage.SetPixel(x, y, pixel); + } + else + { + var r = Math.Abs(expectedPixel.Red - actualPixel.Red); + var g = Math.Abs(expectedPixel.Green - actualPixel.Green); + var b = Math.Abs(expectedPixel.Blue - actualPixel.Blue); + var sum = r + g + b; + + if (sum > pixelColorShiftTolerance) + { + maskImage.SetPixel(x, y, pixel); + } + else + { + maskImage.SetPixel(x, y, 0); + } + } } - maskImage.SetPixel(x, y, new SKColor(red, green, blue, 0)); } } return maskImage; diff --git a/SkiaSharpCompareTestNunit/SkiaSharpCompareTests.cs b/SkiaSharpCompareTestNunit/SkiaSharpCompareTests.cs index b5efcf5..84871f9 100644 --- a/SkiaSharpCompareTestNunit/SkiaSharpCompareTests.cs +++ b/SkiaSharpCompareTestNunit/SkiaSharpCompareTests.cs @@ -217,22 +217,22 @@ public void ShouldVerifyThatImageStreamsAreSemiEqual(string pathPic1, string pat [TestCase(pngBlack4x4px, pngWhite2x2px, ResizeOption.Resize, TransparencyOptions.IgnoreAlphaChannel)] [TestCase(renderedForm1, renderedForm2, ResizeOption.Resize, TransparencyOptions.IgnoreAlphaChannel)] [TestCase(renderedForm2, renderedForm1, ResizeOption.Resize, TransparencyOptions.IgnoreAlphaChannel)] - public void Diffmask(string pathPic1, string pathPic2, ResizeOption resizeOption, TransparencyOptions transparancyOPtions) + public void Diffmask(string pathPic1, string pathPic2, ResizeOption resizeOption, TransparencyOptions transparencyOptions) { - var sut = new ImageCompare(resizeOption, transparancyOPtions); + var sut = new ImageCompare(resizeOption, transparencyOptions); var absolutePathPic1 = Path.Combine(AppContext.BaseDirectory, pathPic1); var absolutePathPic2 = Path.Combine(AppContext.BaseDirectory, pathPic2); - using var absolutPic1 = SKBitmap.Decode(absolutePathPic1); - using var absolutPic2 = SKBitmap.Decode(absolutePathPic2); + using var absolutePic1 = SKBitmap.Decode(absolutePathPic1); + using var absolutePic2 = SKBitmap.Decode(absolutePathPic2); var differenceMask = Path.GetTempFileName() + "differenceMask.png"; using (var fileStreamDifferenceMask = File.Create(differenceMask)) - using (var maskImage = sut.CalcDiffMaskImage(absolutPic1, absolutPic2)) + using (var maskImage = sut.CalcDiffMaskImage(absolutePic1, absolutePic2)) { SaveAsPng(maskImage, fileStreamDifferenceMask); } - var maskedDiff = Compare.CalcDiff(absolutePathPic1, absolutePathPic2, differenceMask, resizeOption); + var maskedDiff = Compare.CalcDiff(absolutePathPic1, absolutePathPic2, differenceMask, resizeOption, 0, transparencyOptions); File.Delete(differenceMask); Assert.That(maskedDiff.AbsoluteError, Is.EqualTo(0), "AbsoluteError"); @@ -241,7 +241,7 @@ public void Diffmask(string pathPic1, string pathPic2, ResizeOption resizeOption Assert.That(maskedDiff.PixelErrorPercentage, Is.EqualTo(0), "PixelErrorPercentage"); } - private void SaveAsPng(SKBitmap maskImage, FileStream fileStreamDifferenceMask) + private static void SaveAsPng(SKBitmap maskImage, FileStream fileStreamDifferenceMask) { var encodedData = maskImage.Encode(SKEncodedImageFormat.Png, 100); encodedData.SaveTo(fileStreamDifferenceMask); diff --git a/SkiaSharpCompareTestNunit/SkiaSharpStaticCompareTests.cs b/SkiaSharpCompareTestNunit/SkiaSharpStaticCompareTests.cs index 693b454..2be8ace 100644 --- a/SkiaSharpCompareTestNunit/SkiaSharpStaticCompareTests.cs +++ b/SkiaSharpCompareTestNunit/SkiaSharpStaticCompareTests.cs @@ -201,7 +201,7 @@ public void ShouldVerifyThatImageStreamsAreSemiEqual(string pathPic1, string pat [TestCase(pngBlack4x4px, pngWhite2x2px, 0, 0, 0, 0, ResizeOption.Resize)] [TestCase(renderedForm1, renderedForm2, 0, 0, 0, 0, ResizeOption.Resize)] [TestCase(renderedForm2, renderedForm1, 0, 0, 0, 0, ResizeOption.Resize)] - public void Diffmask(string pathPic1, string pathPic2, int expectedMeanError, int expectedAbsoluteError, int expectedPixelErrorCount, double expectedPixelErrorPercentage, ResizeOption resizeOption) + public void DiffMask(string pathPic1, string pathPic2, int expectedMeanError, int expectedAbsoluteError, int expectedPixelErrorCount, double expectedPixelErrorPercentage, ResizeOption resizeOption) { var absolutePathPic1 = Path.Combine(AppContext.BaseDirectory, pathPic1); var absolutePathPic2 = Path.Combine(AppContext.BaseDirectory, pathPic2); @@ -210,7 +210,7 @@ public void Diffmask(string pathPic1, string pathPic2, int expectedMeanError, in using (var fileStreamDifferenceMask = File.Create(differenceMask)) using (var maskImage = Compare.CalcDiffMaskImage(absolutePathPic1, absolutePathPic2, resizeOption)) { - IntegrationTest.SaveAsPng(maskImage, fileStreamDifferenceMask); + SaveAsPng(maskImage, fileStreamDifferenceMask); } var maskedDiff = Compare.CalcDiff(absolutePathPic1, absolutePathPic2, differenceMask, resizeOption); @@ -244,7 +244,7 @@ public void ShouldCalcDiffMaskSKBitmapAndUseOutcome(string pathPic1, string path using (var fileStreamDifferenceMask = File.Create(differenceMaskPicPath)) using (var maskImage = Compare.CalcDiffMaskImage(absolutePic1, absolutePic2, resizeOption)) { - IntegrationTest.SaveAsPng(maskImage, fileStreamDifferenceMask); + SaveAsPng(maskImage, fileStreamDifferenceMask); } using var differenceMaskPic = SKBitmap.Decode(differenceMaskPicPath); From 3d134577443878e793dede462ed1a083dc5c304b Mon Sep 17 00:00:00 2001 From: Stefan Seeland <168659+stesee@users.noreply.github.com> Date: Mon, 6 Oct 2025 01:59:38 +0200 Subject: [PATCH 14/19] Add XML docs and centralize IsImageEntirelyBlack utility Added XML documentation comments to ImageCompare and TransparencyOptions for improved code clarity. Moved the IsImageEntirelyBlack method to ImageExtensions and updated all usages in test files to use the new location, removing duplicate implementations. Also added new tests for black image diff masks. --- SkiaSharpCompare/ImageCompare.cs | 38 ++++++++++++++ SkiaSharpCompare/TransparencyOptions.cs | 14 +++++ SkiaSharpCompareTestNunit/ImageExtensions.cs | 17 +++++++ .../SkiaSharpCompareTests.cs | 51 +++++++++++-------- .../SkiaSharpStaticCompareTests.cs | 44 ++++++++-------- 5 files changed, 120 insertions(+), 44 deletions(-) diff --git a/SkiaSharpCompare/ImageCompare.cs b/SkiaSharpCompare/ImageCompare.cs index 206a3cc..4a895b3 100644 --- a/SkiaSharpCompare/ImageCompare.cs +++ b/SkiaSharpCompare/ImageCompare.cs @@ -3,8 +3,25 @@ namespace Codeuctivity.SkiaSharpCompare { + /// + /// Provides functionality for comparing images and calculating differences between them. + /// + /// The class allows users to compare images using various options, + /// such as resizing, transparency handling, and pixel color shift tolerance. It supports multiple input types, + /// including file paths, streams, and in-memory bitmaps. The class also provides methods to generate difference + /// masks and check for image equality or size equality. public class ImageCompare { + /// + /// Initializes a new instance of the class with the specified options for resizing, + /// transparency handling, and color shift tolerance. + /// + /// Specifies how images should be resized before comparison. The default is . + /// Specifies how transparency should be handled during comparison. The default is . + /// Specifies the tolerance for color shifts in pixel values during comparison. A value of 0 means no + /// tolerance, and higher values allow for greater differences. The default is 0. public ImageCompare(ResizeOption resizeOption = ResizeOption.DontResize, TransparencyOptions transparencyOptions = TransparencyOptions.CompareAlphaChannel, int pixelColorShiftTolerance = 0) { ResizeOption = resizeOption; @@ -12,10 +29,31 @@ public ImageCompare(ResizeOption resizeOption = ResizeOption.DontResize, Transpa PixelColorShiftTolerance = pixelColorShiftTolerance; } + /// + /// Gets the resize option that determines how an image should be resized. + /// public ResizeOption ResizeOption { get; } + + /// + /// Gets the transparency options that determine how transparency should be handled during image comparison. + /// public TransparencyOptions TransparencyOptions { get; } + + /// + /// Gets the tolerance level for pixel color shifts in image processing operations. + /// + /// This property is typically used to determine whether two pixels are considered + /// similar in color during image comparison or analysis tasks. public int PixelColorShiftTolerance { get; } + /// + /// Calculates the difference between two images located at the specified file paths. + /// + /// The comparison process may involve resizing the images or applying tolerances for + /// pixel color shifts and transparency, depending on the configured options. + /// The absolute file path to the first image. This cannot be null or empty. + /// The absolute file path to the second image. This cannot be null or empty. + /// An object representing the differences between the two images. public ICompareResult CalcDiff(string absolutePathPic1, string absolutePathPic2) { return Compare.CalcDiff(absolutePathPic1, absolutePathPic2, ResizeOption, PixelColorShiftTolerance, TransparencyOptions); diff --git a/SkiaSharpCompare/TransparencyOptions.cs b/SkiaSharpCompare/TransparencyOptions.cs index 91a1e65..3da27dd 100644 --- a/SkiaSharpCompare/TransparencyOptions.cs +++ b/SkiaSharpCompare/TransparencyOptions.cs @@ -1,8 +1,22 @@ namespace Codeuctivity.SkiaSharpCompare { + /// + /// Specifies options for handling transparency when comparing images. + /// + /// These options determine whether the alpha channel (transparency) is considered during image + /// comparison. public enum TransparencyOptions { + /// + /// Gets or sets a value indicating whether the alpha channel should be ignored during processing. + /// IgnoreAlphaChannel, + + /// + /// Compares the alpha channel values of two colors. + /// + /// This method is useful for sorting or comparing colors based on their transparency + /// levels. CompareAlphaChannel } } \ No newline at end of file diff --git a/SkiaSharpCompareTestNunit/ImageExtensions.cs b/SkiaSharpCompareTestNunit/ImageExtensions.cs index 8e94a3e..205af4d 100644 --- a/SkiaSharpCompareTestNunit/ImageExtensions.cs +++ b/SkiaSharpCompareTestNunit/ImageExtensions.cs @@ -14,5 +14,22 @@ internal static void SaveAsPng(SKBitmap diffMask1Image, FileStream diffMask1Stre var encodedData = diffMask1Image.Encode(SKEncodedImageFormat.Png, 100); encodedData.SaveTo(diffMask1Stream); } + + public static bool IsImageEntirelyBlack(SKBitmap image) + { + for (var x = 0; x < image.Width; x++) + { + for (var y = 0; y < image.Height; y++) + { + var sKColor = image.GetPixel(x, y); + if (sKColor.Red != 0 || sKColor.Green != 0 || sKColor.Blue != 0 || sKColor.Alpha != 0) + { + return false; + } + } + } + + return true; + } } } \ No newline at end of file diff --git a/SkiaSharpCompareTestNunit/SkiaSharpCompareTests.cs b/SkiaSharpCompareTestNunit/SkiaSharpCompareTests.cs index 84871f9..8f1f33b 100644 --- a/SkiaSharpCompareTestNunit/SkiaSharpCompareTests.cs +++ b/SkiaSharpCompareTestNunit/SkiaSharpCompareTests.cs @@ -208,6 +208,25 @@ public void ShouldVerifyThatImageStreamsAreSemiEqual(string pathPic1, string pat Assert.That(diff.PixelErrorPercentage, Is.EqualTo(expectedPixelErrorPercentage), "PixelErrorPercentage"); } + [TestCase(pngBlack2x2px, pngBlack2x2px, 0, 0, 0, 0, ResizeOption.Resize, true)] + [TestCase(pngBlack2x2px, pngBlack2x2px, 0, 0, 0, 0, ResizeOption.DontResize, true)] + [TestCase(pngBlack2x2px, pngBlack4x4px, 0, 0, 0, 0, ResizeOption.Resize, true)] + [TestCase(pngBlack2x2px, pngWhite2x2px, 0, 0, 0, 0, ResizeOption.DontResize, false)] + public void ShouldCalcDiffMaskSKBitmap(string pathPic1, string pathPic2, int expectedMeanError, int expectedAbsoluteError, int expectedPixelErrorCount, double expectedPixelErrorPercentage, ResizeOption resizeOption, bool expectedOutcome) + { + var sut = new ImageCompare(resizeOption); + var absolutePathPic1 = Path.Combine(AppContext.BaseDirectory, pathPic1); + var absolutePathPic2 = Path.Combine(AppContext.BaseDirectory, pathPic2); + + using var absolutePic1 = SKBitmap.Decode(absolutePathPic1); + using var absolutePic2 = SKBitmap.Decode(absolutePathPic2); + + using (var maskImage = sut.CalcDiffMaskImage(absolutePic1, absolutePic2)) + { + Assert.That(ImageExtensions.IsImageEntirelyBlack(maskImage), Is.EqualTo(expectedOutcome)); + } + } + [TestCase(png0Rgba32, png1Rgba32, null, TransparencyOptions.IgnoreAlphaChannel)] [TestCase(pngWhite2x2px, pngBlack2x2px, ResizeOption.Resize, TransparencyOptions.CompareAlphaChannel)] [TestCase(pngTransparent2x2px, pngPartialTransparent2x2px, ResizeOption.Resize, TransparencyOptions.CompareAlphaChannel)] @@ -217,11 +236,15 @@ public void ShouldVerifyThatImageStreamsAreSemiEqual(string pathPic1, string pat [TestCase(pngBlack4x4px, pngWhite2x2px, ResizeOption.Resize, TransparencyOptions.IgnoreAlphaChannel)] [TestCase(renderedForm1, renderedForm2, ResizeOption.Resize, TransparencyOptions.IgnoreAlphaChannel)] [TestCase(renderedForm2, renderedForm1, ResizeOption.Resize, TransparencyOptions.IgnoreAlphaChannel)] - public void Diffmask(string pathPic1, string pathPic2, ResizeOption resizeOption, TransparencyOptions transparencyOptions) + public void CalcDiff(string pathPic1, string pathPic2, ResizeOption resizeOption, TransparencyOptions transparencyOptions) { var sut = new ImageCompare(resizeOption, transparencyOptions); var absolutePathPic1 = Path.Combine(AppContext.BaseDirectory, pathPic1); var absolutePathPic2 = Path.Combine(AppContext.BaseDirectory, pathPic2); + + var maskImage1 = Compare.CalcDiffMaskImage(absolutePathPic1, absolutePathPic2, ResizeOption.Resize); + Assert.That(ImageExtensions.IsImageEntirelyBlack(maskImage1), Is.False); + using var absolutePic1 = SKBitmap.Decode(absolutePathPic1); using var absolutePic2 = SKBitmap.Decode(absolutePathPic2); var differenceMask = Path.GetTempFileName() + "differenceMask.png"; @@ -229,6 +252,7 @@ public void Diffmask(string pathPic1, string pathPic2, ResizeOption resizeOption using (var fileStreamDifferenceMask = File.Create(differenceMask)) using (var maskImage = sut.CalcDiffMaskImage(absolutePic1, absolutePic2)) { + Assert.That(ImageExtensions.IsImageEntirelyBlack(maskImage), Is.False); SaveAsPng(maskImage, fileStreamDifferenceMask); } @@ -331,7 +355,7 @@ public void ShouldThrowUsingInvalidImageDimensionsDiffMask(string pathPic1, stri } [TestCase(png0Rgba32, png1Rgba32, 0, 0, 0, 0)] - public void DiffMaskStreams(string pathPic1, string pathPic2, int expectedMeanError, int expectedAbsoluteError, int expectedPixelErrorCount, double expectedPixelErrorPercentage) + public void CalcDiffStreams(string pathPic1, string pathPic2, int expectedMeanError, int expectedAbsoluteError, int expectedPixelErrorCount, double expectedPixelErrorPercentage) { var sut = new ImageCompare(transparencyOptions: TransparencyOptions.IgnoreAlphaChannel); var absolutePathPic1 = Path.Combine(AppContext.BaseDirectory, pathPic1); @@ -378,7 +402,7 @@ public void CalcDiffMaskImage_WhenSupplyingDiffMaskOfTwoImagesByFilePath_NoDiffe ImageExtensions.SaveAsPng(diffMask2Image, diffMask2Stream); } - Assert.That(IsImageEntirelyBlack(diffMask2Image), Is.True); + Assert.That(ImageExtensions.IsImageEntirelyBlack(diffMask2Image), Is.True); File.Delete(diffMask1Path); } @@ -407,7 +431,7 @@ public void CalcDiffMaskImage_WhenSupplyingDiffMaskOfTwoImagesByStream_NoDiffere { diffMask1Stream.Position = 0; using var diffMask2Image = sut.CalcDiffMaskImage(image1Stream, image2Stream, diffMask1Stream); - Assert.That(IsImageEntirelyBlack(diffMask2Image), Is.True); + Assert.That(ImageExtensions.IsImageEntirelyBlack(diffMask2Image), Is.True); } File.Delete(diffMask1Path); @@ -426,7 +450,7 @@ public void CalcDiffMaskImage_WhenSupplyingDiffMaskOfTwoImagesByImage_NoDifferen using var diffMask1Image = sut.CalcDiffMaskImage(image1, image2); using var diffMask2Image = sut.CalcDiffMaskImage(image1, image2, diffMask1Image); - Assert.That(IsImageEntirelyBlack(diffMask2Image), Is.True); + Assert.That(ImageExtensions.IsImageEntirelyBlack(diffMask2Image), Is.True); } [Test] @@ -495,22 +519,5 @@ public void ShouldVerifyThatImageWithDifferentSizeThrows(string pathPic1, string Assert.That(exception?.Message, Is.EqualTo("Size of images differ.")); } - - private static bool IsImageEntirelyBlack(SKBitmap image) - { - for (var x = 0; x < image.Width; x++) - { - for (var y = 0; y < image.Height; y++) - { - var sKColor = image.GetPixel(x, y); - if (sKColor.Red != 0 || sKColor.Green != 0 || sKColor.Blue != 0 || sKColor.Alpha != 0) - { - return false; - } - } - } - - return true; - } } } \ No newline at end of file diff --git a/SkiaSharpCompareTestNunit/SkiaSharpStaticCompareTests.cs b/SkiaSharpCompareTestNunit/SkiaSharpStaticCompareTests.cs index 2be8ace..34605e1 100644 --- a/SkiaSharpCompareTestNunit/SkiaSharpStaticCompareTests.cs +++ b/SkiaSharpCompareTestNunit/SkiaSharpStaticCompareTests.cs @@ -228,6 +228,23 @@ private static void SaveAsPng(SKBitmap maskImage, FileStream fileStreamDifferenc encodedData.SaveTo(fileStreamDifferenceMask); } + [TestCase(pngBlack2x2px, pngBlack2x2px, 0, 0, 0, 0, ResizeOption.Resize)] + [TestCase(pngBlack2x2px, pngBlack2x2px, 0, 0, 0, 0, ResizeOption.DontResize)] + [TestCase(pngBlack2x2px, pngBlack4x4px, 0, 0, 0, 0, ResizeOption.Resize)] + public void ShouldCalcDiffMaskSKBitmap(string pathPic1, string pathPic2, int expectedMeanError, int expectedAbsoluteError, int expectedPixelErrorCount, double expectedPixelErrorPercentage, ResizeOption resizeOption) + { + var absolutePathPic1 = Path.Combine(AppContext.BaseDirectory, pathPic1); + var absolutePathPic2 = Path.Combine(AppContext.BaseDirectory, pathPic2); + + using var absolutePic1 = SKBitmap.Decode(absolutePathPic1); + using var absolutePic2 = SKBitmap.Decode(absolutePathPic2); + + using (var maskImage = Compare.CalcDiffMaskImage(absolutePic1, absolutePic2, resizeOption)) + { + Assert.That(ImageExtensions.IsImageEntirelyBlack(maskImage)); + } + } + [TestCase(png0Rgba32, png1Rgba32, 0, 0, 0, 0, ResizeOption.DontResize)] [TestCase(jpg0Rgb24, jpg1Rgb24, 0, 0, 0, 0, ResizeOption.DontResize)] [TestCase(jpg0Rgb24, jpg1Rgb24, 0, 0, 0, 0, ResizeOption.Resize)] @@ -341,7 +358,7 @@ public void CalcDiffMaskImage_WhenSupplyingDiffMaskOfTwoImagesByFilePath_NoDiffe ImageExtensions.SaveAsPng(diffMask2Image, diffMask2Stream); } - Assert.That(IsImageEntirelyBlack(diffMask2Image), Is.EqualTo(expectIsImageEntirelyBlack)); + Assert.That(ImageExtensions.IsImageEntirelyBlack(diffMask2Image), Is.EqualTo(expectIsImageEntirelyBlack)); File.Delete(diffMask1Path); } @@ -369,7 +386,7 @@ public void CalcDiffMaskImage_WhenSupplyingDiffMaskOfTwoImagesByStream_NoDiffere { diffMask1Stream.Position = 0; using var diffMask2Image = Compare.CalcDiffMaskImage(image1Stream, image2Stream, diffMask1Stream); - Assert.That(IsImageEntirelyBlack(diffMask2Image), Is.True); + Assert.That(ImageExtensions.IsImageEntirelyBlack(diffMask2Image), Is.True); } File.Delete(diffMask1Path); @@ -391,8 +408,8 @@ public void CalcDiffMaskImage_WhenSupplyingDiffMaskOfTwoImagesByImage_NoDifferen using var diffMask2Image = Compare.CalcDiffMaskImage(image1, image3, diffMask1Image, pixelColorShiftTolerance: expectedPixelColorShiftTolerance); - Assert.That(IsImageEntirelyBlack(diffMask1Image), Is.EqualTo(expectToleranceMaskToEntirelyBlack)); - Assert.That(IsImageEntirelyBlack(diffMask2Image), Is.True); + Assert.That(ImageExtensions.IsImageEntirelyBlack(diffMask1Image), Is.EqualTo(expectToleranceMaskToEntirelyBlack)); + Assert.That(ImageExtensions.IsImageEntirelyBlack(diffMask2Image), Is.True); } [TestCase(png0Rgba32, png0Rgba32, 0)] @@ -408,7 +425,7 @@ public void CalcDiffMaskImage_ToleranceColorShift_NoDifferences(string image1Rel using var diffMask1Image = Compare.CalcDiffMaskImage(image1, image2, pixelColorShiftTolerance: expectedPixelColorShiftTolerance); - Assert.That(IsImageEntirelyBlack(diffMask1Image), Is.True); + Assert.That(ImageExtensions.IsImageEntirelyBlack(diffMask1Image), Is.True); } [Test] @@ -468,22 +485,5 @@ public void ShouldVerifyThatImageWithDifferentSizeThrows(string pathPic1, string Assert.That(exception?.Message, Is.EqualTo("Size of images differ.")); } - - private static bool IsImageEntirelyBlack(SKBitmap image) - { - for (var x = 0; x < image.Width; x++) - { - for (var y = 0; y < image.Height; y++) - { - var sKColor = image.GetPixel(x, y); - if (sKColor.Red != 0 || sKColor.Green != 0 || sKColor.Blue != 0) - { - return false; - } - } - } - - return true; - } } } \ No newline at end of file From 1c9352b4751d8f399960e9fd73c1c886bdb21f6a Mon Sep 17 00:00:00 2001 From: Stefan Seeland <168659+stesee@users.noreply.github.com> Date: Mon, 6 Oct 2025 02:32:19 +0200 Subject: [PATCH 15/19] Refactor IsImageEntirelyBlack to support transparency options Updated IsImageEntirelyBlack to accept a TransparencyOptions parameter and adjusted all usages in tests to pass the correct option. Improved mask pixel alpha handling in SkiaSharpCompare to ensure full opacity for color differences when comparing alpha channels. --- SkiaSharpCompare/SkiaSharpCompare.cs | 4 +++- SkiaSharpCompareTestNunit/ImageExtensions.cs | 4 ++-- SkiaSharpCompareTestNunit/SkiaSharpCompareTests.cs | 12 ++++++------ .../SkiaSharpStaticCompareTests.cs | 12 ++++++------ 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/SkiaSharpCompare/SkiaSharpCompare.cs b/SkiaSharpCompare/SkiaSharpCompare.cs index 24a435a..c6b2329 100644 --- a/SkiaSharpCompare/SkiaSharpCompare.cs +++ b/SkiaSharpCompare/SkiaSharpCompare.cs @@ -527,7 +527,9 @@ public static SKBitmap CalcDiffMaskImage(SKBitmap actual, SKBitmap expected, Res if (transparencyOptions == TransparencyOptions.CompareAlphaChannel) { var alpha = (byte)Math.Abs(actualPixel.Alpha - expectedPixel.Alpha); - var pixel = new SKColor(red, green, blue, alpha); + // Ensure mask pixel has full opacity if there's any color difference + var effectiveAlpha = (red > 0 || green > 0 || blue > 0) ? (byte)255 : alpha; + var pixel = new SKColor(red, green, blue, effectiveAlpha); maskImage.SetPixel(x, y, pixel); } else diff --git a/SkiaSharpCompareTestNunit/ImageExtensions.cs b/SkiaSharpCompareTestNunit/ImageExtensions.cs index 205af4d..cbc9050 100644 --- a/SkiaSharpCompareTestNunit/ImageExtensions.cs +++ b/SkiaSharpCompareTestNunit/ImageExtensions.cs @@ -15,14 +15,14 @@ internal static void SaveAsPng(SKBitmap diffMask1Image, FileStream diffMask1Stre encodedData.SaveTo(diffMask1Stream); } - public static bool IsImageEntirelyBlack(SKBitmap image) + public static bool IsImageEntirelyBlack(SKBitmap image, Codeuctivity.SkiaSharpCompare.TransparencyOptions transparencyOptions) { for (var x = 0; x < image.Width; x++) { for (var y = 0; y < image.Height; y++) { var sKColor = image.GetPixel(x, y); - if (sKColor.Red != 0 || sKColor.Green != 0 || sKColor.Blue != 0 || sKColor.Alpha != 0) + if (sKColor.Red != 0 || sKColor.Green != 0 || sKColor.Blue != 0 || (transparencyOptions == Codeuctivity.SkiaSharpCompare.TransparencyOptions.CompareAlphaChannel && sKColor.Alpha != 0)) { return false; } diff --git a/SkiaSharpCompareTestNunit/SkiaSharpCompareTests.cs b/SkiaSharpCompareTestNunit/SkiaSharpCompareTests.cs index 8f1f33b..4cb41f2 100644 --- a/SkiaSharpCompareTestNunit/SkiaSharpCompareTests.cs +++ b/SkiaSharpCompareTestNunit/SkiaSharpCompareTests.cs @@ -223,7 +223,7 @@ public void ShouldCalcDiffMaskSKBitmap(string pathPic1, string pathPic2, int exp using (var maskImage = sut.CalcDiffMaskImage(absolutePic1, absolutePic2)) { - Assert.That(ImageExtensions.IsImageEntirelyBlack(maskImage), Is.EqualTo(expectedOutcome)); + Assert.That(ImageExtensions.IsImageEntirelyBlack(maskImage, TransparencyOptions.IgnoreAlphaChannel), Is.EqualTo(expectedOutcome)); } } @@ -243,7 +243,7 @@ public void CalcDiff(string pathPic1, string pathPic2, ResizeOption resizeOption var absolutePathPic2 = Path.Combine(AppContext.BaseDirectory, pathPic2); var maskImage1 = Compare.CalcDiffMaskImage(absolutePathPic1, absolutePathPic2, ResizeOption.Resize); - Assert.That(ImageExtensions.IsImageEntirelyBlack(maskImage1), Is.False); + Assert.That(ImageExtensions.IsImageEntirelyBlack(maskImage1, transparencyOptions), Is.False); using var absolutePic1 = SKBitmap.Decode(absolutePathPic1); using var absolutePic2 = SKBitmap.Decode(absolutePathPic2); @@ -252,7 +252,7 @@ public void CalcDiff(string pathPic1, string pathPic2, ResizeOption resizeOption using (var fileStreamDifferenceMask = File.Create(differenceMask)) using (var maskImage = sut.CalcDiffMaskImage(absolutePic1, absolutePic2)) { - Assert.That(ImageExtensions.IsImageEntirelyBlack(maskImage), Is.False); + Assert.That(ImageExtensions.IsImageEntirelyBlack(maskImage, transparencyOptions), Is.False); SaveAsPng(maskImage, fileStreamDifferenceMask); } @@ -402,7 +402,7 @@ public void CalcDiffMaskImage_WhenSupplyingDiffMaskOfTwoImagesByFilePath_NoDiffe ImageExtensions.SaveAsPng(diffMask2Image, diffMask2Stream); } - Assert.That(ImageExtensions.IsImageEntirelyBlack(diffMask2Image), Is.True); + Assert.That(ImageExtensions.IsImageEntirelyBlack(diffMask2Image, transparencyOptions), Is.True); File.Delete(diffMask1Path); } @@ -431,7 +431,7 @@ public void CalcDiffMaskImage_WhenSupplyingDiffMaskOfTwoImagesByStream_NoDiffere { diffMask1Stream.Position = 0; using var diffMask2Image = sut.CalcDiffMaskImage(image1Stream, image2Stream, diffMask1Stream); - Assert.That(ImageExtensions.IsImageEntirelyBlack(diffMask2Image), Is.True); + Assert.That(ImageExtensions.IsImageEntirelyBlack(diffMask2Image, TransparencyOptions.IgnoreAlphaChannel), Is.True); } File.Delete(diffMask1Path); @@ -450,7 +450,7 @@ public void CalcDiffMaskImage_WhenSupplyingDiffMaskOfTwoImagesByImage_NoDifferen using var diffMask1Image = sut.CalcDiffMaskImage(image1, image2); using var diffMask2Image = sut.CalcDiffMaskImage(image1, image2, diffMask1Image); - Assert.That(ImageExtensions.IsImageEntirelyBlack(diffMask2Image), Is.True); + Assert.That(ImageExtensions.IsImageEntirelyBlack(diffMask2Image, TransparencyOptions.IgnoreAlphaChannel), Is.True); } [Test] diff --git a/SkiaSharpCompareTestNunit/SkiaSharpStaticCompareTests.cs b/SkiaSharpCompareTestNunit/SkiaSharpStaticCompareTests.cs index 34605e1..4984f22 100644 --- a/SkiaSharpCompareTestNunit/SkiaSharpStaticCompareTests.cs +++ b/SkiaSharpCompareTestNunit/SkiaSharpStaticCompareTests.cs @@ -241,7 +241,7 @@ public void ShouldCalcDiffMaskSKBitmap(string pathPic1, string pathPic2, int exp using (var maskImage = Compare.CalcDiffMaskImage(absolutePic1, absolutePic2, resizeOption)) { - Assert.That(ImageExtensions.IsImageEntirelyBlack(maskImage)); + Assert.That(ImageExtensions.IsImageEntirelyBlack(maskImage, TransparencyOptions.IgnoreAlphaChannel)); } } @@ -358,7 +358,7 @@ public void CalcDiffMaskImage_WhenSupplyingDiffMaskOfTwoImagesByFilePath_NoDiffe ImageExtensions.SaveAsPng(diffMask2Image, diffMask2Stream); } - Assert.That(ImageExtensions.IsImageEntirelyBlack(diffMask2Image), Is.EqualTo(expectIsImageEntirelyBlack)); + Assert.That(ImageExtensions.IsImageEntirelyBlack(diffMask2Image, TransparencyOptions.IgnoreAlphaChannel), Is.EqualTo(expectIsImageEntirelyBlack)); File.Delete(diffMask1Path); } @@ -386,7 +386,7 @@ public void CalcDiffMaskImage_WhenSupplyingDiffMaskOfTwoImagesByStream_NoDiffere { diffMask1Stream.Position = 0; using var diffMask2Image = Compare.CalcDiffMaskImage(image1Stream, image2Stream, diffMask1Stream); - Assert.That(ImageExtensions.IsImageEntirelyBlack(diffMask2Image), Is.True); + Assert.That(ImageExtensions.IsImageEntirelyBlack(diffMask2Image, TransparencyOptions.IgnoreAlphaChannel), Is.True); } File.Delete(diffMask1Path); @@ -408,8 +408,8 @@ public void CalcDiffMaskImage_WhenSupplyingDiffMaskOfTwoImagesByImage_NoDifferen using var diffMask2Image = Compare.CalcDiffMaskImage(image1, image3, diffMask1Image, pixelColorShiftTolerance: expectedPixelColorShiftTolerance); - Assert.That(ImageExtensions.IsImageEntirelyBlack(diffMask1Image), Is.EqualTo(expectToleranceMaskToEntirelyBlack)); - Assert.That(ImageExtensions.IsImageEntirelyBlack(diffMask2Image), Is.True); + Assert.That(ImageExtensions.IsImageEntirelyBlack(diffMask1Image, TransparencyOptions.IgnoreAlphaChannel), Is.EqualTo(expectToleranceMaskToEntirelyBlack)); + Assert.That(ImageExtensions.IsImageEntirelyBlack(diffMask2Image, TransparencyOptions.IgnoreAlphaChannel), Is.True); } [TestCase(png0Rgba32, png0Rgba32, 0)] @@ -425,7 +425,7 @@ public void CalcDiffMaskImage_ToleranceColorShift_NoDifferences(string image1Rel using var diffMask1Image = Compare.CalcDiffMaskImage(image1, image2, pixelColorShiftTolerance: expectedPixelColorShiftTolerance); - Assert.That(ImageExtensions.IsImageEntirelyBlack(diffMask1Image), Is.True); + Assert.That(ImageExtensions.IsImageEntirelyBlack(diffMask1Image, TransparencyOptions.IgnoreAlphaChannel), Is.True); } [Test] From 5bb821ff4239f371522550f40d9ccc6b51e768b8 Mon Sep 17 00:00:00 2001 From: Stefan Seeland <168659+stesee@users.noreply.github.com> Date: Mon, 6 Oct 2025 02:44:14 +0200 Subject: [PATCH 16/19] Update test cases and assertions in diff mask tests Adjusted expected values in TestCase attributes for CalcDiffMaskImage tests and added an assertion for IsImageEntirelyBlack on the first diff mask image. Simplified the assertion for the second diff mask image to only check if it is entirely black, removing the expected value comparison. --- SkiaSharpCompareTestNunit/SkiaSharpStaticCompareTests.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/SkiaSharpCompareTestNunit/SkiaSharpStaticCompareTests.cs b/SkiaSharpCompareTestNunit/SkiaSharpStaticCompareTests.cs index 4984f22..31e7182 100644 --- a/SkiaSharpCompareTestNunit/SkiaSharpStaticCompareTests.cs +++ b/SkiaSharpCompareTestNunit/SkiaSharpStaticCompareTests.cs @@ -336,9 +336,9 @@ public void DiffMaskStreams(string pathPic1, string pathPic2, int expectedMeanEr Assert.That(maskedDiff.PixelErrorPercentage, Is.EqualTo(expectedPixelErrorPercentage), "PixelErrorPercentage"); } - [TestCase(png0Rgba32, png1Rgba32, 0, true)] - [TestCase(colorShift1, colorShift2, 20, false)] - [TestCase(colorShift1, colorShift2, 0, true)] + [TestCase(png0Rgba32, png1Rgba32, 0, false)] + [TestCase(colorShift1, colorShift2, 20, true)] + [TestCase(colorShift1, colorShift2, 0, false)] public void CalcDiffMaskImage_WhenSupplyingDiffMaskOfTwoImagesByFilePath_NoDifferences(string image1RelativePath, string image2RelativePath, int pixelColorShiftTolerance, bool expectIsImageEntirelyBlack) { var image1Path = Path.Combine(AppContext.BaseDirectory, image1RelativePath); @@ -349,6 +349,7 @@ public void CalcDiffMaskImage_WhenSupplyingDiffMaskOfTwoImagesByFilePath_NoDiffe { using var diffMask1Image = Compare.CalcDiffMaskImage(image1Path, image2Path, ResizeOption.DontResize, pixelColorShiftTolerance); ImageExtensions.SaveAsPng(diffMask1Image, diffMask1Stream); + Assert.That(ImageExtensions.IsImageEntirelyBlack(diffMask1Image, TransparencyOptions.IgnoreAlphaChannel), Is.EqualTo(expectIsImageEntirelyBlack)); } using var diffMask2Image = Compare.CalcDiffMaskImage(image1Path, image2Path, diffMask1Path); @@ -358,7 +359,7 @@ public void CalcDiffMaskImage_WhenSupplyingDiffMaskOfTwoImagesByFilePath_NoDiffe ImageExtensions.SaveAsPng(diffMask2Image, diffMask2Stream); } - Assert.That(ImageExtensions.IsImageEntirelyBlack(diffMask2Image, TransparencyOptions.IgnoreAlphaChannel), Is.EqualTo(expectIsImageEntirelyBlack)); + Assert.That(ImageExtensions.IsImageEntirelyBlack(diffMask2Image, TransparencyOptions.IgnoreAlphaChannel)); File.Delete(diffMask1Path); } From 3d09a01e3732f33c093bcfd6b335e58ad2d8bdd5 Mon Sep 17 00:00:00 2001 From: Stefan Seeland <168659+stesee@users.noreply.github.com> Date: Mon, 6 Oct 2025 02:55:18 +0200 Subject: [PATCH 17/19] Use sut instance for CalcDiffMaskImage call Replaced Compare.CalcDiffMaskImage with sut.CalcDiffMaskImage in the test to ensure the method is called on the correct instance. Removed the ResizeOption.Resize argument for consistency with the method signature. --- SkiaSharpCompareTestNunit/SkiaSharpCompareTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SkiaSharpCompareTestNunit/SkiaSharpCompareTests.cs b/SkiaSharpCompareTestNunit/SkiaSharpCompareTests.cs index 4cb41f2..952fa75 100644 --- a/SkiaSharpCompareTestNunit/SkiaSharpCompareTests.cs +++ b/SkiaSharpCompareTestNunit/SkiaSharpCompareTests.cs @@ -242,7 +242,7 @@ public void CalcDiff(string pathPic1, string pathPic2, ResizeOption resizeOption var absolutePathPic1 = Path.Combine(AppContext.BaseDirectory, pathPic1); var absolutePathPic2 = Path.Combine(AppContext.BaseDirectory, pathPic2); - var maskImage1 = Compare.CalcDiffMaskImage(absolutePathPic1, absolutePathPic2, ResizeOption.Resize); + var maskImage1 = sut.CalcDiffMaskImage(absolutePathPic1, absolutePathPic2); Assert.That(ImageExtensions.IsImageEntirelyBlack(maskImage1, transparencyOptions), Is.False); using var absolutePic1 = SKBitmap.Decode(absolutePathPic1); From de59b240da24515ef29fc8d4e35f7353e51efe07 Mon Sep 17 00:00:00 2001 From: Stefan Seeland <168659+stesee@users.noreply.github.com> Date: Mon, 6 Oct 2025 03:19:47 +0200 Subject: [PATCH 18/19] Improve diff mask calculation and update API Updated CalcDiffMaskImage methods to include TransparencyOptions parameter for more flexible image comparison. Improved pixel difference calculation in CalcDiff to prevent negative values by using Math.Max, ensuring more accurate diff mask results. Also clarified XML documentation for CalcDiff. --- SkiaSharpCompare/ImageCompare.cs | 94 +++++++++++++++++++++++++++- SkiaSharpCompare/SkiaSharpCompare.cs | 18 +++--- 2 files changed, 103 insertions(+), 9 deletions(-) diff --git a/SkiaSharpCompare/ImageCompare.cs b/SkiaSharpCompare/ImageCompare.cs index 4a895b3..8974315 100644 --- a/SkiaSharpCompare/ImageCompare.cs +++ b/SkiaSharpCompare/ImageCompare.cs @@ -59,71 +59,161 @@ public ICompareResult CalcDiff(string absolutePathPic1, string absolutePathPic2) return Compare.CalcDiff(absolutePathPic1, absolutePathPic2, ResizeOption, PixelColorShiftTolerance, TransparencyOptions); } + /// + /// Calculates the difference between two images located at the specified file paths, using a provided difference mask. + /// + /// + /// + /// + /// public ICompareResult CalcDiff(string absolutePathPic1, string absolutePathPic2, string differenceMask) { return Compare.CalcDiff(absolutePathPic1, absolutePathPic2, differenceMask, ResizeOption, PixelColorShiftTolerance, TransparencyOptions); } + /// + /// Calculates the difference between two in-memory images represented as objects. + /// + /// + /// + /// + /// public ICompareResult CalcDiff(SKBitmap absolutePic1, SKBitmap absolutePic2, SKBitmap differenceMaskPic) { return Compare.CalcDiff(absolutePic1, absolutePic2, differenceMaskPic, ResizeOption, PixelColorShiftTolerance, TransparencyOptions); } + /// + /// Calculates the difference between two in-memory images represented as objects. + /// + /// + /// + /// public ICompareResult CalcDiff(FileStream pic1, FileStream pic2) { return Compare.CalcDiff(pic1, pic2, ResizeOption, PixelColorShiftTolerance, TransparencyOptions); } + /// + /// Calculates the difference between two images provided as file streams, using a specified mask image to focus the comparison. + /// + /// + /// + /// + /// public ICompareResult CalcDiff(FileStream pic1, FileStream pic2, SKBitmap maskImage) { return Compare.CalcDiff(pic1, pic2, maskImage, ResizeOption, PixelColorShiftTolerance, TransparencyOptions); } + /// + /// Calculates a difference mask image that highlights the differences between two images located at the specified file paths. + /// + /// + /// + /// public SKBitmap CalcDiffMaskImage(string absolutePathPic1, string absolutePathPic2) { return Compare.CalcDiffMaskImage(absolutePathPic1, absolutePathPic2, ResizeOption, PixelColorShiftTolerance, TransparencyOptions); } + /// + /// Calculates a difference mask image that highlights the differences between two in-memory images represented as objects. + /// + /// + /// + /// public SKBitmap CalcDiffMaskImage(SKBitmap absolutePic1, SKBitmap absolutePic2) { return Compare.CalcDiffMaskImage(absolutePic1, absolutePic2, ResizeOption, PixelColorShiftTolerance, TransparencyOptions); } + /// + /// Calculates a difference mask image that highlights the differences between two images located at the specified file paths, + /// + /// + /// + /// + /// public SKBitmap CalcDiffMaskImage(string image1Path, string image2Path, string diffMask1Path) { - return Compare.CalcDiffMaskImage(image1Path, image2Path, diffMask1Path, ResizeOption, PixelColorShiftTolerance); + return Compare.CalcDiffMaskImage(image1Path, image2Path, diffMask1Path, ResizeOption, PixelColorShiftTolerance, TransparencyOptions); } + /// + /// Calculates a difference mask image that highlights the differences between two images provided as file streams. + /// + /// + /// + /// public SKBitmap CalcDiffMaskImage(FileStream image1Stream, FileStream image2Stream) { - return Compare.CalcDiffMaskImage(image1Stream, image2Stream, ResizeOption, PixelColorShiftTolerance); + return Compare.CalcDiffMaskImage(image1Stream, image2Stream, ResizeOption, PixelColorShiftTolerance, TransparencyOptions); } + /// + /// Calculates a difference mask image that highlights the differences between two images provided as file streams, + /// + /// + /// + /// + /// public SKBitmap CalcDiffMaskImage(FileStream image1Stream, FileStream image2Stream, FileStream diffMask1Stream) { return Compare.CalcDiffMaskImage(image1Stream, image2Stream, diffMask1Stream, ResizeOption, PixelColorShiftTolerance, TransparencyOptions); } + /// + /// Calculates a difference mask image that highlights the differences between two in-memory images represented as objects, + /// + /// + /// + /// + /// public SKBitmap CalcDiffMaskImage(SKBitmap image1, SKBitmap image2, SKBitmap diffMask1Image) { return Compare.CalcDiffMaskImage(image1, image2, diffMask1Image, ResizeOption, PixelColorShiftTolerance, TransparencyOptions); } + /// + /// Checks if two images located at the specified file paths are identical, considering the configured options for resizing, + /// + /// + /// + /// public bool ImagesAreEqual(string absolutePathActual, string absolutePathExpected) { return Compare.ImagesAreEqual(absolutePathActual, absolutePathExpected, ResizeOption, PixelColorShiftTolerance, TransparencyOptions); } + /// + /// Checks if two images provided as file streams are identical, considering the configured options for resizing, + /// + /// + /// + /// public bool ImagesAreEqual(FileStream actual, FileStream expected) { return Compare.ImagesAreEqual(actual, expected, ResizeOption, PixelColorShiftTolerance, TransparencyOptions); } + /// + /// Checks if two in-memory images represented as objects are identical, considering the configured options for resizing, + /// + /// + /// + /// public bool ImagesAreEqual(SKBitmap actual, SKBitmap expected) { return Compare.ImagesAreEqual(actual, expected, ResizeOption, PixelColorShiftTolerance, TransparencyOptions); } + /// + /// Checks if two images have the same dimensions (width and height). + /// + /// + /// + /// public bool ImagesHaveEqualSize(string absolutePathActual, string absolutePathExpected) { return Compare.ImagesHaveEqualSize(absolutePathActual, absolutePathExpected); diff --git a/SkiaSharpCompare/SkiaSharpCompare.cs b/SkiaSharpCompare/SkiaSharpCompare.cs index c6b2329..afe9ba9 100644 --- a/SkiaSharpCompare/SkiaSharpCompare.cs +++ b/SkiaSharpCompare/SkiaSharpCompare.cs @@ -278,7 +278,7 @@ public static ICompareResult CalcDiff(Stream actualImage, Stream expectedImage, } /// - /// Calculates ICompareResult expressing the amount of difference of both images using a image mask for tolerated difference between the two images + /// Compares two images for equivalence /// /// /// @@ -286,7 +286,7 @@ public static ICompareResult CalcDiff(Stream actualImage, Stream expectedImage, /// /// /// - /// Mean and absolute pixel error + /// public static ICompareResult CalcDiff(SKBitmap actual, SKBitmap expected, SKBitmap maskImage, ResizeOption resizeOption = ResizeOption.DontResize, int pixelColorShiftTolerance = 0, TransparencyOptions transparencyOptions = TransparencyOptions.CompareAlphaChannel) { ArgumentNullException.ThrowIfNull(maskImage); @@ -367,7 +367,6 @@ public static ICompareResult CalcDiff(SKBitmap actual, SKBitmap expected, SKBitm private static bool ImagesHaveSameDimension(SKBitmap actual, SKBitmap expected) { ArgumentNullException.ThrowIfNull(actual); - ArgumentNullException.ThrowIfNull(expected); return actual.Height == expected.Height && actual.Width == expected.Width; @@ -627,13 +626,18 @@ public static SKBitmap CalcDiffMaskImage(SKBitmap actual, SKBitmap expected, SKB var expectedPixel = expected.GetPixel(x, y); var maskPixel = mask.GetPixel(x, y); - var red = (byte)(Math.Abs(actualPixel.Red - expectedPixel.Red) - maskPixel.Red); - var green = (byte)(Math.Abs(actualPixel.Green - expectedPixel.Green) - maskPixel.Green); - var blue = (byte)(Math.Abs(actualPixel.Blue - expectedPixel.Blue) - maskPixel.Blue); + var redDiff = Math.Abs(actualPixel.Red - expectedPixel.Red); + var greenDiff = Math.Abs(actualPixel.Green - expectedPixel.Green); + var blueDiff = Math.Abs(actualPixel.Blue - expectedPixel.Blue); + + var red = (byte)Math.Max(0, redDiff - maskPixel.Red); + var green = (byte)Math.Max(0, greenDiff - maskPixel.Green); + var blue = (byte)Math.Max(0, blueDiff - maskPixel.Blue); if (transparencyOptions == TransparencyOptions.CompareAlphaChannel) { - var alpha = (byte)(Math.Abs(actualPixel.Alpha - expectedPixel.Alpha) - maskPixel.Alpha); + var alphaDiff = Math.Abs(actualPixel.Alpha - expectedPixel.Alpha); + var alpha = (byte)Math.Max(0, alphaDiff - maskPixel.Alpha); var pixel = new SKColor(red, green, blue, alpha); if (pixelColorShiftTolerance == 0) From ec345812e00d09d94f73df56188c4202a2474605 Mon Sep 17 00:00:00 2001 From: Stefan Seeland <168659+stesee@users.noreply.github.com> Date: Mon, 6 Oct 2025 08:22:43 +0200 Subject: [PATCH 19/19] Make ImagesHaveEqualSize static and update tests Changed ImagesHaveEqualSize to a static method in ImageCompare and updated all related test cases to call it statically, removing unnecessary instance creation. --- SkiaSharpCompare/ImageCompare.cs | 2 +- SkiaSharpCompareTestNunit/SkiaSharpCompareTests.cs | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/SkiaSharpCompare/ImageCompare.cs b/SkiaSharpCompare/ImageCompare.cs index 8974315..4696815 100644 --- a/SkiaSharpCompare/ImageCompare.cs +++ b/SkiaSharpCompare/ImageCompare.cs @@ -214,7 +214,7 @@ public bool ImagesAreEqual(SKBitmap actual, SKBitmap expected) /// /// /// - public bool ImagesHaveEqualSize(string absolutePathActual, string absolutePathExpected) + public static bool ImagesHaveEqualSize(string absolutePathActual, string absolutePathExpected) { return Compare.ImagesHaveEqualSize(absolutePathActual, absolutePathExpected); } diff --git a/SkiaSharpCompareTestNunit/SkiaSharpCompareTests.cs b/SkiaSharpCompareTestNunit/SkiaSharpCompareTests.cs index 952fa75..afd4505 100644 --- a/SkiaSharpCompareTestNunit/SkiaSharpCompareTests.cs +++ b/SkiaSharpCompareTestNunit/SkiaSharpCompareTests.cs @@ -30,11 +30,10 @@ public class SkiaSharpCompareTests [TestCase(png0Rgba32, pngBlack2x2px, false)] public void ShouldVerifyThatImagesFromFilePathSizeAreEqual(string pathActual, string pathExpected, bool expectedOutcome) { - var sut = new ImageCompare(); var absolutePathActual = Path.Combine(AppContext.BaseDirectory, pathActual); var absolutePathExpected = Path.Combine(AppContext.BaseDirectory, pathExpected); - Assert.That(sut.ImagesHaveEqualSize(absolutePathActual, absolutePathExpected), Is.EqualTo(expectedOutcome)); + Assert.That(ImageCompare.ImagesHaveEqualSize(absolutePathActual, absolutePathExpected), Is.EqualTo(expectedOutcome)); } [Test] @@ -45,14 +44,13 @@ public void ShouldVerifyThatImagesFromFilePathSizeAreEqual(string pathActual, st [TestCase(png0Rgba32, pngBlack2x2px, false)] public void ShouldVerifyThatImagesSizeAreEqual(string pathActual, string pathExpected, bool expectedOutcome) { - var sut = new ImageCompare(); var absolutePathActual = Path.Combine(AppContext.BaseDirectory, pathActual); var absolutePathExpected = Path.Combine(AppContext.BaseDirectory, pathExpected); using var actual = SKBitmap.Decode(absolutePathActual); using var expected = SKBitmap.Decode(absolutePathExpected); - Assert.That(sut.ImagesHaveEqualSize(absolutePathActual, absolutePathExpected), Is.EqualTo(expectedOutcome)); + Assert.That(ImageCompare.ImagesHaveEqualSize(absolutePathActual, absolutePathExpected), Is.EqualTo(expectedOutcome)); } [Test] @@ -63,14 +61,13 @@ public void ShouldVerifyThatImagesSizeAreEqual(string pathActual, string pathExp [TestCase(png0Rgba32, pngBlack2x2px, false)] public void ShouldVerifyThatImageStreamsSizeAreEqual(string pathActual, string pathExpected, bool expectedOutcome) { - var sut = new ImageCompare(); var absolutePathActual = Path.Combine(AppContext.BaseDirectory, pathActual); var absolutePathExpected = Path.Combine(AppContext.BaseDirectory, pathExpected); using var actual = new FileStream(absolutePathActual, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); using var expected = new FileStream(absolutePathExpected, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - Assert.That(sut.ImagesHaveEqualSize(absolutePathActual, absolutePathExpected), Is.EqualTo(expectedOutcome)); + Assert.That(ImageCompare.ImagesHaveEqualSize(absolutePathActual, absolutePathExpected), Is.EqualTo(expectedOutcome)); } [Test]