|
| 1 | +# Pixel Comparer |
| 2 | + |
| 3 | +> [!TIP] |
| 4 | +> **[Try it live →](../sample/pixelcomparer)** — Interactive Pixel Comparer demo in the Blazor sample app. |
| 5 | +
|
| 6 | +Pixel Comparer lets you compare two images pixel by pixel and quantify their differences. It's ideal for visual regression testing, screenshot comparison in CI pipelines, and verifying that rendering output matches expected results. |
| 7 | + |
| 8 | +## Quick Start |
| 9 | + |
| 10 | +### Compare two images |
| 11 | + |
| 12 | +```csharp |
| 13 | +using SkiaSharp; |
| 14 | +using SkiaSharp.Extended; |
| 15 | + |
| 16 | +var result = SKPixelComparer.Compare("expected.png", "actual.png"); |
| 17 | + |
| 18 | +Console.WriteLine($"Total pixels: {result.TotalPixels}"); |
| 19 | +Console.WriteLine($"Error pixels: {result.ErrorPixelCount}"); |
| 20 | +Console.WriteLine($"Error percentage: {result.ErrorPixelPercentage:P2}"); |
| 21 | +Console.WriteLine($"Absolute error: {result.AbsoluteError}"); |
| 22 | +``` |
| 23 | + |
| 24 | +### Generate a difference mask |
| 25 | + |
| 26 | +```csharp |
| 27 | +using var mask = SKPixelComparer.GenerateDifferenceMask("expected.png", "actual.png"); |
| 28 | + |
| 29 | +// Encode and save the mask |
| 30 | +using var data = mask.Encode(SKEncodedImageFormat.Png, 100); |
| 31 | +File.WriteAllBytes("diff-mask.png", data.ToArray()); |
| 32 | +``` |
| 33 | + |
| 34 | +The mask is a black-and-white image where **white pixels** indicate differences and **black pixels** indicate matching areas. |
| 35 | + |
| 36 | +## How It Works |
| 37 | + |
| 38 | +The comparer normalizes both images to BGRA8888 format, then walks through every pixel and sums the per-channel (red, green, blue) absolute differences: |
| 39 | + |
| 40 | +1. For each pixel, compute `|R₁ − R₂| + |G₁ − G₂| + |B₁ − B₂|` |
| 41 | +2. If the sum is greater than zero, that pixel is counted as an error |
| 42 | +3. The total per-pixel sums are accumulated into `AbsoluteError` |
| 43 | + |
| 44 | +Both images must have the same dimensions; otherwise an `InvalidOperationException` is thrown. |
| 45 | + |
| 46 | +## Comparison Results |
| 47 | + |
| 48 | +The [`SKPixelComparisonResult`](xref:SkiaSharp.Extended.SKPixelComparisonResult) contains: |
| 49 | + |
| 50 | +| Property | Type | Description | |
| 51 | +| :------- | :--- | :---------- | |
| 52 | +| `TotalPixels` | `int` | Width × Height of the images | |
| 53 | +| `ErrorPixelCount` | `int` | Number of pixels with any difference | |
| 54 | +| `ErrorPixelPercentage` | `double` | `ErrorPixelCount / TotalPixels` | |
| 55 | +| `AbsoluteError` | `int` | Sum of all per-channel differences | |
| 56 | + |
| 57 | +## Mask-Based Comparison |
| 58 | + |
| 59 | +When comparing images that have expected minor differences (e.g., anti-aliasing, compression artifacts), you can supply a tolerance mask. The mask image uses per-channel thresholds—a difference is only counted if it exceeds the corresponding channel value in the mask pixel: |
| 60 | + |
| 61 | +```csharp |
| 62 | +using var expected = SKImage.FromEncodedData("expected.png"); |
| 63 | +using var actual = SKImage.FromEncodedData("actual.png"); |
| 64 | +using var mask = SKImage.FromEncodedData("tolerance-mask.png"); |
| 65 | + |
| 66 | +var result = SKPixelComparer.Compare(expected, actual, mask); |
| 67 | +``` |
| 68 | + |
| 69 | +For example, if a mask pixel has RGB values of `(10, 10, 10)`, differences of up to 10 per channel at that location are ignored. This lets you define region-specific tolerances. |
| 70 | + |
| 71 | +## Input Overloads |
| 72 | + |
| 73 | +All comparison methods accept multiple input types for convenience: |
| 74 | + |
| 75 | +| Input Type | Example | |
| 76 | +| :--------- | :------ | |
| 77 | +| File paths | `Compare("a.png", "b.png")` | |
| 78 | +| `SKImage` | `Compare(imageA, imageB)` | |
| 79 | +| `SKBitmap` | `Compare(bitmapA, bitmapB)` | |
| 80 | +| `SKPixmap` | `Compare(pixmapA, pixmapB)` | |
| 81 | + |
| 82 | +The same overloads are available for `GenerateDifferenceMask` (without mask support) and for the three-argument masked `Compare`. |
| 83 | + |
| 84 | +## Usage Patterns |
| 85 | + |
| 86 | +### Visual regression testing |
| 87 | + |
| 88 | +Use the comparer in your test suite to catch unintended rendering changes: |
| 89 | + |
| 90 | +```csharp |
| 91 | +[Fact] |
| 92 | +public void ChartRendering_MatchesBaseline() |
| 93 | +{ |
| 94 | + using var actual = RenderChart(testData); |
| 95 | + using var expected = SKImage.FromEncodedData("baselines/chart.png"); |
| 96 | + |
| 97 | + var result = SKPixelComparer.Compare(expected, actual); |
| 98 | + |
| 99 | + Assert.Equal(0, result.ErrorPixelCount); |
| 100 | +} |
| 101 | +``` |
| 102 | + |
| 103 | +### CI screenshot comparison with tolerance |
| 104 | + |
| 105 | +For tests that may have minor platform-specific differences: |
| 106 | + |
| 107 | +```csharp |
| 108 | +[Fact] |
| 109 | +public void UI_MatchesBaseline_WithTolerance() |
| 110 | +{ |
| 111 | + using var actual = CaptureScreenshot(); |
| 112 | + using var expected = SKImage.FromEncodedData("baselines/screen.png"); |
| 113 | + |
| 114 | + var result = SKPixelComparer.Compare(expected, actual); |
| 115 | + |
| 116 | + // Allow up to 0.5% pixel difference |
| 117 | + Assert.True(result.ErrorPixelPercentage < 0.005, |
| 118 | + $"Too many differing pixels: {result.ErrorPixelPercentage:P2}"); |
| 119 | +} |
| 120 | +``` |
| 121 | + |
| 122 | +### Saving a difference mask for debugging |
| 123 | + |
| 124 | +When a comparison fails, generate and save the mask to help diagnose what changed: |
| 125 | + |
| 126 | +```csharp |
| 127 | +if (result.ErrorPixelCount > 0) |
| 128 | +{ |
| 129 | + using var diffMask = SKPixelComparer.GenerateDifferenceMask(expected, actual); |
| 130 | + using var encoded = diffMask.Encode(SKEncodedImageFormat.Png, 100); |
| 131 | + File.WriteAllBytes("test-output/diff-mask.png", encoded.ToArray()); |
| 132 | +} |
| 133 | +``` |
| 134 | + |
| 135 | +## Learn More |
| 136 | + |
| 137 | +- [API Reference — SKPixelComparer](xref:SkiaSharp.Extended.SKPixelComparer) — Full method documentation |
| 138 | +- [API Reference — SKPixelComparisonResult](xref:SkiaSharp.Extended.SKPixelComparisonResult) — Result class documentation |
0 commit comments