// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.IO;
using System.Numerics;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using Xunit;
namespace SixLabors.ImageSharp.Tests
{
public static class TestImageExtensions
{
///
/// TODO: This should be a common processing method! The image.Opacity(val) multiplies the alpha channel!
///
///
///
public static void MakeOpaque(this IImageProcessingContext ctx)
where TPixel : struct, IPixel
{
MemoryManager memoryManager = ctx.MemoryManager;
ctx.Apply(img =>
{
using (Buffer2D temp = memoryManager.Allocate2D(img.Width, img.Height))
{
Span tempSpan = temp.Span;
foreach (ImageFrame frame in img.Frames)
{
Span pixelSpan = frame.GetPixelSpan();
PixelOperations.Instance.ToVector4(pixelSpan, tempSpan, pixelSpan.Length);
for (int i = 0; i < tempSpan.Length; i++)
{
ref Vector4 v = ref tempSpan[i];
v.W = 1.0f;
}
PixelOperations.Instance.PackFromVector4(tempSpan, pixelSpan, pixelSpan.Length);
}
}
});
}
///
/// Saves the image only when not running in the CI server.
///
/// The pixel format
/// The image
/// The image provider
/// Details to be concatenated to the test output file, describing the parameters of the test.
/// The extension
/// A boolean indicating whether to append the pixel type to the output file name.
/// A boolean indicating whether to append to the test output file name.
public static Image DebugSave(
this Image image,
ITestImageProvider provider,
object testOutputDetails = null,
string extension = "png",
bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true)
where TPixel : struct, IPixel
{
if (TestEnvironment.RunsOnCI)
{
return image;
}
// We are running locally then we want to save it out
provider.Utility.SaveTestOutputFile(
image,
extension,
testOutputDetails: testOutputDetails,
appendPixelTypeToFileName: appendPixelTypeToFileName,
appendSourceFileOrDescription: appendSourceFileOrDescription);
return image;
}
///
/// Saves the image only when not running in the CI server.
///
/// The pixel format
/// The image
/// The image provider
/// The image encoder
/// Details to be concatenated to the test output file, describing the parameters of the test.
/// A boolean indicating whether to append the pixel type to the output file name.
public static Image DebugSave(
this Image image,
ITestImageProvider provider,
IImageEncoder encoder,
object testOutputDetails = null,
bool appendPixelTypeToFileName = true)
where TPixel : struct, IPixel
{
if (TestEnvironment.RunsOnCI)
{
return image;
}
// We are running locally then we want to save it out
provider.Utility.SaveTestOutputFile(
image,
encoder: encoder,
testOutputDetails: testOutputDetails,
appendPixelTypeToFileName: appendPixelTypeToFileName);
return image;
}
public static Image DebugSaveMultiFrame(
this Image image,
ITestImageProvider provider,
object testOutputDetails = null,
string extension = "png",
bool appendPixelTypeToFileName = true)
where TPixel : struct, IPixel
{
if (TestEnvironment.RunsOnCI)
{
return image;
}
// We are running locally then we want to save it out
provider.Utility.SaveTestOutputFileMultiFrame(
image,
extension,
testOutputDetails: testOutputDetails,
appendPixelTypeToFileName: appendPixelTypeToFileName);
return image;
}
///
/// Compares the image against the expected Reference output, throws an exception if the images are not similar enough.
/// The output file should be named identically to the output produced by .
///
/// The pixel format
/// The image
/// The image provider
/// Details to be concatenated to the test output file, describing the parameters of the test.
/// The extension
/// A boolean indicating whether we should debug save + compare against a grayscale image, smaller in size.
/// A boolean indicating whether to append the pixel type to the output file name.
/// A custom for the verification
///
public static Image CompareToReferenceOutput(
this Image image,
ITestImageProvider provider,
object testOutputDetails = null,
string extension = "png",
bool grayscale = false,
bool appendPixelTypeToFileName = true)
where TPixel : struct, IPixel
{
return CompareToReferenceOutput(
image,
ImageComparer.Tolerant(),
provider,
testOutputDetails,
extension,
grayscale,
appendPixelTypeToFileName);
}
///
/// Compares the image against the expected Reference output, throws an exception if the images are not similar enough.
/// The output file should be named identically to the output produced by .
///
/// The pixel format
/// The image
/// The to use
/// The image provider
/// Details to be concatenated to the test output file, describing the parameters of the test.
/// The extension
/// A boolean indicating whether we should debug save + compare against a grayscale image, smaller in size.
/// A boolean indicating whether to append the pixel type to the output file name.
///
public static Image CompareToReferenceOutput(
this Image image,
ImageComparer comparer,
ITestImageProvider provider,
object testOutputDetails = null,
string extension = "png",
bool grayscale = false,
bool appendPixelTypeToFileName = true)
where TPixel : struct, IPixel
{
using (Image referenceImage = GetReferenceOutputImage(
provider,
testOutputDetails,
extension,
appendPixelTypeToFileName))
{
comparer.VerifySimilarity(referenceImage, image);
}
return image;
}
public static Image CompareFirstFrameToReferenceOutput(
this Image image,
ImageComparer comparer,
ITestImageProvider provider,
object testOutputDetails = null,
string extension = "png",
bool grayscale = false,
bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true)
where TPixel : struct, IPixel
{
using (var firstFrameOnlyImage = new Image(image.Width, image.Height))
using (Image referenceImage = GetReferenceOutputImage(
provider,
testOutputDetails,
extension,
appendPixelTypeToFileName,
appendSourceFileOrDescription))
{
firstFrameOnlyImage.Frames.AddFrame(image.Frames.RootFrame);
firstFrameOnlyImage.Frames.RemoveFrame(0);
comparer.VerifySimilarity(referenceImage, firstFrameOnlyImage);
}
return image;
}
public static Image CompareToReferenceOutputMultiFrame(
this Image image,
ITestImageProvider provider,
ImageComparer comparer,
object testOutputDetails = null,
string extension = "png",
bool grayscale = false,
bool appendPixelTypeToFileName = true)
where TPixel : struct, IPixel
{
using (Image referenceImage = GetReferenceOutputImageMultiFrame(
provider,
image.Frames.Count,
testOutputDetails,
extension,
appendPixelTypeToFileName))
{
comparer.VerifySimilarity(referenceImage, image);
}
return image;
}
public static Image GetReferenceOutputImage(this ITestImageProvider provider,
object testOutputDetails = null,
string extension = "png",
bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true)
where TPixel : struct, IPixel
{
string referenceOutputFile = provider.Utility.GetReferenceOutputFileName(
extension,
testOutputDetails,
appendPixelTypeToFileName,
appendSourceFileOrDescription);
if (!File.Exists(referenceOutputFile))
{
throw new Exception("Reference output file missing: " + referenceOutputFile);
}
IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(referenceOutputFile);
return Image.Load(referenceOutputFile, decoder);
}
public static Image GetReferenceOutputImageMultiFrame(this ITestImageProvider provider,
int frameCount,
object testOutputDetails = null,
string extension = "png",
bool appendPixelTypeToFileName = true)
where TPixel : struct, IPixel
{
string[] frameFiles = provider.Utility.GetReferenceOutputFileNamesMultiFrame(
frameCount,
extension,
testOutputDetails,
appendPixelTypeToFileName);
var temporaryFrameImages = new List>();
IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(frameFiles[0]);
foreach (string path in frameFiles)
{
if (!File.Exists(path))
{
throw new Exception("Reference output file missing: " + path);
}
var tempImage = Image.Load(path, decoder);
temporaryFrameImages.Add(tempImage);
}
Image firstTemp = temporaryFrameImages[0];
var result = new Image(firstTemp.Width, firstTemp.Height);
foreach (Image fi in temporaryFrameImages)
{
result.Frames.AddFrame(fi.Frames.RootFrame);
fi.Dispose();
}
// remove the initial empty frame:
result.Frames.RemoveFrame(0);
return result;
}
public static IEnumerable GetReferenceOutputSimilarityReports(
this Image image,
ITestImageProvider provider,
ImageComparer comparer,
object testOutputDetails = null,
string extension = "png",
bool appendPixelTypeToFileName = true)
where TPixel : struct, IPixel
{
using (Image referenceImage = provider.GetReferenceOutputImage(
testOutputDetails,
extension,
appendPixelTypeToFileName))
{
return comparer.CompareImages(referenceImage, image);
}
}
public static Image ComparePixelBufferTo(
this Image image,
Span expectedPixels)
where TPixel : struct, IPixel
{
Span actualPixels = image.GetPixelSpan();
Assert.True(expectedPixels.Length == actualPixels.Length, "Buffer sizes are not equal!");
for (int i = 0; i < expectedPixels.Length; i++)
{
Assert.True(expectedPixels[i].Equals(actualPixels[i]), $"Pixels are different on position {i}!");
}
return image;
}
public static Image ComparePixelBufferTo(this Image image, TPixel expectedPixel)
where TPixel : struct, IPixel
{
foreach (ImageFrame imageFrame in image.Frames)
{
imageFrame.ComparePixelBufferTo(expectedPixel);
}
return image;
}
public static ImageFrame ComparePixelBufferTo(this ImageFrame imageFrame, TPixel expectedPixel)
where TPixel : struct, IPixel
{
Span actualPixels = imageFrame.GetPixelSpan();
for (int i = 0; i < actualPixels.Length; i++)
{
Assert.True(expectedPixel.Equals(actualPixels[i]), $"Pixels are different on position {i}!");
}
return imageFrame;
}
public static ImageFrame ComparePixelBufferTo(
this ImageFrame image,
Span expectedPixels)
where TPixel : struct, IPixel
{
Span actual = image.GetPixelSpan();
Assert.True(expectedPixels.Length == actual.Length, "Buffer sizes are not equal!");
for (int i = 0; i < expectedPixels.Length; i++)
{
Assert.True(expectedPixels[i].Equals(actual[i]), $"Pixels are different on position {i}!");
}
return image;
}
public static Image CompareToOriginal(
this Image image,
ITestImageProvider provider)
where TPixel : struct, IPixel
{
return CompareToOriginal(image, provider, ImageComparer.Tolerant());
}
public static Image CompareToOriginal(
this Image image,
ITestImageProvider provider,
ImageComparer comparer)
where TPixel : struct, IPixel
{
string path = TestImageProvider.GetFilePathOrNull(provider);
if (path == null)
{
throw new InvalidOperationException("CompareToOriginal() works only with file providers!");
}
var testFile = TestFile.Create(path);
IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(path);
IImageFormat format = TestEnvironment.GetImageFormat(path);
IImageDecoder defaultDecoder = Configuration.Default.ImageFormatsManager.FindDecoder(format);
//if (referenceDecoder.GetType() == defaultDecoder.GetType())
//{
// throw new InvalidOperationException($"Can't use CompareToOriginal(): no actual reference decoder registered for {format.Name}");
//}
using (var original = Image.Load(testFile.Bytes, referenceDecoder))
{
comparer.VerifySimilarity(original, image);
}
return image;
}
///
/// Loads the expected image with a reference decoder + compares it to .
/// Also performs a debug save using .
///
internal static void VerifyEncoder(this Image image,
ITestImageProvider provider,
string extension,
object testOutputDetails,
IImageEncoder encoder,
ImageComparer customComparer = null,
bool appendPixelTypeToFileName = true,
string referenceImageExtension = null
)
where TPixel : struct, IPixel
{
string actualOutputFile = provider.Utility.SaveTestOutputFile(image, extension, encoder, testOutputDetails, appendPixelTypeToFileName);
IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile);
using (var actualImage = Image.Load(actualOutputFile, referenceDecoder))
{
ImageComparer comparer = customComparer ?? ImageComparer.Exact;
comparer.VerifySimilarity(actualImage, image);
}
}
internal static Image ToGrayscaleImage(this Buffer2D buffer, float scale)
{
var image = new Image(buffer.Width, buffer.Height);
Span pixels = image.Frames.RootFrame.GetPixelSpan();
Span bufferSpan = buffer.Span;
for (int i = 0; i < bufferSpan.Length; i++)
{
float value = bufferSpan[i] * scale;
var v = new Vector4(value, value, value, 1f);
pixels[i].PackFromVector4(v);
}
return image;
}
}
}