| File: Helpers\LinuxPngComparer.cs | Web Access |
| Project: src\tests\Aspire.Tray.Tests\Aspire.Tray.Tests.csproj (Aspire.Tray.Tests) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Runtime.InteropServices; namespace Aspire.Tray.Tests.Helpers; internal static partial class LinuxPngComparer { internal static async Task<CompareResult> Compare( Stream received, Stream verified, IReadOnlyDictionary<string, object> _) { using var directory = new TestTrayStateDirectory(); var receivedPath = directory.CreateAppHost("received.png"); var verifiedPath = directory.CreateAppHost("verified.png"); await using (var output = File.Create(receivedPath)) { await received.CopyToAsync(output); } await using (var output = File.Create(verifiedPath)) { await verified.CopyToAsync(output); } return HaveSamePixels(receivedPath, verifiedPath) ? CompareResult.Equal : CompareResult.NotEqual("Decoded Cairo pixels differ."); } internal static bool HaveSamePixels(string received, string verified) { var actual = Gtk.cairo_image_surface_create_from_png(received); var expected = Gtk.cairo_image_surface_create_from_png(verified); try { if (Gtk.cairo_surface_status(actual) != 0 || Gtk.cairo_surface_status(expected) != 0) { throw new InvalidOperationException("Could not decode the tray artwork for pixel comparison."); } var width = Gtk.cairo_image_surface_get_width(actual); var height = Gtk.cairo_image_surface_get_height(actual); if (width != Gtk.cairo_image_surface_get_width(expected) || height != Gtk.cairo_image_surface_get_height(expected) || cairo_image_surface_get_format(actual) != cairo_image_surface_get_format(expected)) { return false; } // Cairo decodes PNGs as ARGB32 or RGB24, both with four bytes per pixel. // Compare each row without including any stride padding. // https://www.cairographics.org/manual/cairo-Image-Surfaces.html var actualRow = new byte[checked(width * 4)]; var expectedRow = new byte[actualRow.Length]; var actualData = cairo_image_surface_get_data(actual); var expectedData = cairo_image_surface_get_data(expected); var actualStride = cairo_image_surface_get_stride(actual); var expectedStride = cairo_image_surface_get_stride(expected); for (var row = 0; row < height; row++) { Marshal.Copy(actualData + row * actualStride, actualRow, 0, actualRow.Length); Marshal.Copy(expectedData + row * expectedStride, expectedRow, 0, expectedRow.Length); if (!actualRow.AsSpan().SequenceEqual(expectedRow)) { return false; } } return true; } finally { Gtk.cairo_surface_destroy(actual); Gtk.cairo_surface_destroy(expected); } } [LibraryImport("libcairo.so.2")] private static partial int cairo_image_surface_get_format(nint surface); [LibraryImport("libcairo.so.2")] private static partial nint cairo_image_surface_get_data(nint surface); [LibraryImport("libcairo.so.2")] private static partial int cairo_image_surface_get_stride(nint surface); }