Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add an a image comparer #137

Merged
merged 1 commit into from
Aug 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 211 additions & 0 deletions source/SkiaSharp.Extended/Comparer/SKPixelComparer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
using System;

namespace SkiaSharp.Extended
{
public static class SKPixelComparer
{
public static SKPixelComparisonResult Compare(SKBitmap first, SKBitmap second)
{
using var firstPixmap = first.PeekPixels();
using var secondPixmap = second.PeekPixels();
return Compare(firstPixmap, secondPixmap);
}

public static SKPixelComparisonResult Compare(SKPixmap first, SKPixmap second)
{
using var firstWrapper = SKImage.FromPixels(first);
using var secondWrapper = SKImage.FromPixels(second);
return Compare(firstWrapper, secondWrapper);
}

public static SKPixelComparisonResult Compare(SKImage first, SKImage second)
{
Validate(first, second);

var width = first.Width;
var height = first.Height;

var totalPixels = width * height;
var errorPixels = 0;
var absoluteError = 0;

using var firstBitmap = GetNormalizedBitmap(first);
using var firstPixmap = firstBitmap.PeekPixels();
var firstPixels = firstPixmap.GetPixelSpan<SKColor>();

using var secondBitmap = GetNormalizedBitmap(second);
using var secondPixmap = secondBitmap.PeekPixels();
var secondPixels = secondPixmap.GetPixelSpan<SKColor>();

for (var idx = 0; idx < totalPixels; idx++)
{
var firstPixel = firstPixels[idx];
var secondPixel = secondPixels[idx];

var r = Math.Abs(secondPixel.Red - firstPixel.Red);
var g = Math.Abs(secondPixel.Green - firstPixel.Green);
var b = Math.Abs(secondPixel.Blue - firstPixel.Blue);
var d = r + g + b;

absoluteError += d;
if (d > 0)
errorPixels++;
}

return new SKPixelComparisonResult(totalPixels, errorPixels, absoluteError);
}

public static SKPixelComparisonResult Compare(SKBitmap first, SKBitmap second, SKBitmap mask)
{
using var firstPixmap = first.PeekPixels();
using var secondPixmap = second.PeekPixels();
using var maskPixmap = mask.PeekPixels();
return Compare(firstPixmap, secondPixmap, maskPixmap);
}

public static SKPixelComparisonResult Compare(SKPixmap first, SKPixmap second, SKPixmap mask)
{
using var firstWrapper = SKImage.FromPixels(first);
using var secondWrapper = SKImage.FromPixels(second);
using var maskWrapper = SKImage.FromPixels(mask);
return Compare(firstWrapper, secondWrapper, maskWrapper);
}

public static SKPixelComparisonResult Compare(SKImage first, SKImage second, SKImage mask)
{
Validate(first, second);
ValidateMask(first, mask);

var width = first.Width;
var height = first.Height;

var totalPixels = width * height;
var errorPixels = 0;
var absoluteError = 0;

using var firstBitmap = GetNormalizedBitmap(first);
using var firstPixmap = firstBitmap.PeekPixels();
var firstPixels = firstPixmap.GetPixelSpan<SKColor>();

using var secondBitmap = GetNormalizedBitmap(second);
using var secondPixmap = secondBitmap.PeekPixels();
var secondPixels = secondPixmap.GetPixelSpan<SKColor>();

using var maskBitmap = GetNormalizedBitmap(mask);
using var maskPixmap = maskBitmap.PeekPixels();
var maskPixels = maskPixmap.GetPixelSpan<SKColor>();

for (var idx = 0; idx < totalPixels; idx++)
{
var firstPixel = firstPixels[idx];
var secondPixel = secondPixels[idx];
var maskPixel = maskPixels[idx];

var r = Math.Abs(secondPixel.Red - firstPixel.Red);
var g = Math.Abs(secondPixel.Green - firstPixel.Green);
var b = Math.Abs(secondPixel.Blue - firstPixel.Blue);

var d = 0;
if (r > maskPixel.Red)
d += r;
if (g > maskPixel.Green)
d += g;
if (b > maskPixel.Blue)
d += b;

absoluteError += d;
if (d > 0)
errorPixels++;
}

return new SKPixelComparisonResult(totalPixels, errorPixels, absoluteError);
}

public static SKImage GenerateDifferenceMask(SKBitmap first, SKBitmap second)
{
using var firstPixmap = first.PeekPixels();
using var secondPixmap = second.PeekPixels();
return GenerateDifferenceMask(firstPixmap, secondPixmap);
}

public static SKImage GenerateDifferenceMask(SKPixmap first, SKPixmap second)
{
using var firstWrapper = SKImage.FromPixels(first);
using var secondWrapper = SKImage.FromPixels(second);
return GenerateDifferenceMask(firstWrapper, secondWrapper);
}

public static SKImage GenerateDifferenceMask(SKImage first, SKImage second)
{
Validate(first, second);

var width = first.Width;
var height = first.Height;

var totalPixels = width * height;

using var firstBitmap = GetNormalizedBitmap(first);
using var firstPixmap = firstBitmap.PeekPixels();
var firstPixels = firstPixmap.GetPixelSpan<SKColor>();

using var secondBitmap = GetNormalizedBitmap(second);
using var secondPixmap = secondBitmap.PeekPixels();
var secondPixels = secondPixmap.GetPixelSpan<SKColor>();

var diffBitmap = new SKBitmap(new SKImageInfo(width, height));
using var diffPixmap = diffBitmap.PeekPixels();
var diffPixels = diffPixmap.GetPixelSpan<SKColor>();

for (var idx = 0; idx < totalPixels; idx++)
{
var firstPixel = firstPixels[idx];
var secondPixel = secondPixels[idx];

var r = (byte)Math.Abs(secondPixel.Red - firstPixel.Red);
var g = (byte)Math.Abs(secondPixel.Green - firstPixel.Green);
var b = (byte)Math.Abs(secondPixel.Blue - firstPixel.Blue);

diffPixels[idx] = (r + g + b) > 0 ? SKColors.White : SKColors.Black;
}

return SKImage.FromBitmap(diffBitmap);
}

private static void ValidateMask(SKImage first, SKImage mask)
{
_ = first ?? throw new ArgumentNullException(nameof(first));
_ = mask ?? throw new ArgumentNullException(nameof(mask));

var s1 = first.Info.Size;
var s2 = mask.Info.Size;

if (s1 != s2)
throw new InvalidOperationException($"Unable to compare using mask of a different size: {s1.Width}x{s1.Height} vs {s2.Width}x{s2.Height}.");
}

private static void Validate(SKImage first, SKImage second)
{
_ = first ?? throw new ArgumentNullException(nameof(first));
_ = second ?? throw new ArgumentNullException(nameof(second));

var s1 = first.Info.Size;
var s2 = second.Info.Size;

if (s1 != s2)
throw new InvalidOperationException($"Unable to compare images of different sizes: {s1.Width}x{s1.Height} vs {s2.Width}x{s2.Height}.");
}

private static SKBitmap GetNormalizedBitmap(SKImage image)
{
var width = image.Width;
var height = image.Height;

var bitmap = new SKBitmap(new SKImageInfo(width, height, SKColorType.Bgra8888));

using (var canvas = new SKCanvas(bitmap))
canvas.DrawImage(image, 0, 0);

return bitmap;
}
}
}
21 changes: 21 additions & 0 deletions source/SkiaSharp.Extended/Comparer/SKPixelComparisonResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace SkiaSharp.Extended
{
public class SKPixelComparisonResult
{
public SKPixelComparisonResult(int totalPixels, int errorPixelCount, int absoluteError)
{
TotalPixels = totalPixels;
ErrorPixelCount = errorPixelCount;
AbsoluteError = absoluteError;
}

public int TotalPixels { get; }

public int ErrorPixelCount { get; }

public double ErrorPixelPercentage =>
(double)ErrorPixelCount / TotalPixels;

public int AbsoluteError { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public void NullEncodeThrows()
[InlineData("img7.png", 4, 3, SKBlurHashTest.Image7)]
public void CanEncodeImage(string source, int compX, int compY, string expected)
{
using var img = SKImage.FromEncodedData(Path.Combine("images", source));
using var img = SKImage.FromEncodedData(SKBlurHashTest.GetImagePath(source));

var result = SKBlurHash.Serialize(img, compX, compY);

Expand All @@ -41,7 +41,7 @@ public void CanEncodeImage(string source, int compX, int compY, string expected)
[InlineData("img7.png", 4, 3, SKBlurHashTest.Image7)]
public void CanEncodeBitmap(string source, int compX, int compY, string expected)
{
using var img = SKBitmap.Decode(Path.Combine("images", source));
using var img = SKBitmap.Decode(SKBlurHashTest.GetImagePath(source));

var result = SKBlurHash.Serialize(img, compX, compY);

Expand All @@ -59,7 +59,7 @@ public void CanEncodeBitmap(string source, int compX, int compY, string expected
[InlineData("img7.png", 4, 3, SKBlurHashTest.Image7)]
public void CanEncodePixmap(string source, int compX, int compY, string expected)
{
using var img = SKBitmap.Decode(Path.Combine("images", source));
using var img = SKBitmap.Decode(SKBlurHashTest.GetImagePath(source));
using var pixmap = img.PeekPixels();

var result = SKBlurHash.Serialize(pixmap, compX, compY);
Expand Down
7 changes: 6 additions & 1 deletion tests/SkiaSharp.Extended.Tests/BlurHash/SKBlurHashTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,15 @@ public class SKBlurHashTest
public const string Image6 = "LlMF%n00%#MwS|WCWEM{R*bbWBbH";
public const string Image7 = "LjIY5?00?bIUofWBWBM{WBofWBj[";

public static readonly string BaseImages = Path.Combine("images", "BlurHash");

public static string GetImagePath(string filename) =>
Path.Combine(BaseImages, filename);

[Fact]
public void CanEncodeAndDecode()
{
using var img = SKBitmap.Decode(Path.Combine("images", "img1.jpg"));
using var img = SKBitmap.Decode(GetImagePath("img1.jpg"));

var encoded = SKBlurHash.Serialize(img, 4, 3);

Expand Down
Loading