mirror of https://github.com/SixLabors/ImageSharp
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1173 lines
58 KiB
1173 lines
58 KiB
// Copyright (c) Six Labors.
|
|
// Licensed under the Six Labors Split License.
|
|
|
|
using System.Numerics;
|
|
using SixLabors.ImageSharp.PixelFormats;
|
|
using SixLabors.ImageSharp.PixelFormats.PixelBlenders;
|
|
using SixLabors.ImageSharp.Tests.TestUtilities;
|
|
|
|
namespace SixLabors.ImageSharp.Tests.PixelFormats;
|
|
|
|
[Trait("Category", "PixelFormats")]
|
|
public class PixelBlenderTests
|
|
{
|
|
public static TheoryData<object, Type, PixelColorBlendingMode> BlenderMappings = new()
|
|
{
|
|
{ new TestPixel<Rgba32>(), DefaultPixelBlenders<Rgba32>.NormalSrcOver.GetType(), PixelColorBlendingMode.Normal },
|
|
{ new TestPixel<Rgba32>(), DefaultPixelBlenders<Rgba32>.ScreenSrcOver.GetType(), PixelColorBlendingMode.Screen },
|
|
{ new TestPixel<Rgba32>(), DefaultPixelBlenders<Rgba32>.HardLightSrcOver.GetType(), PixelColorBlendingMode.HardLight },
|
|
{ new TestPixel<Rgba32>(), DefaultPixelBlenders<Rgba32>.OverlaySrcOver.GetType(), PixelColorBlendingMode.Overlay },
|
|
{ new TestPixel<Rgba32>(), DefaultPixelBlenders<Rgba32>.DarkenSrcOver.GetType(), PixelColorBlendingMode.Darken },
|
|
{ new TestPixel<Rgba32>(), DefaultPixelBlenders<Rgba32>.LightenSrcOver.GetType(), PixelColorBlendingMode.Lighten },
|
|
{ new TestPixel<Rgba32>(), DefaultPixelBlenders<Rgba32>.AddSrcOver.GetType(), PixelColorBlendingMode.Add },
|
|
{ new TestPixel<Rgba32>(), DefaultPixelBlenders<Rgba32>.SubtractSrcOver.GetType(), PixelColorBlendingMode.Subtract },
|
|
{ new TestPixel<Rgba32>(), DefaultPixelBlenders<Rgba32>.MultiplySrcOver.GetType(), PixelColorBlendingMode.Multiply },
|
|
{ new TestPixel<RgbaVector>(), DefaultPixelBlenders<RgbaVector>.NormalSrcOver.GetType(), PixelColorBlendingMode.Normal },
|
|
{ new TestPixel<RgbaVector>(), DefaultPixelBlenders<RgbaVector>.ScreenSrcOver.GetType(), PixelColorBlendingMode.Screen },
|
|
{ new TestPixel<RgbaVector>(), DefaultPixelBlenders<RgbaVector>.HardLightSrcOver.GetType(), PixelColorBlendingMode.HardLight },
|
|
{ new TestPixel<RgbaVector>(), DefaultPixelBlenders<RgbaVector>.OverlaySrcOver.GetType(), PixelColorBlendingMode.Overlay },
|
|
{ new TestPixel<RgbaVector>(), DefaultPixelBlenders<RgbaVector>.DarkenSrcOver.GetType(), PixelColorBlendingMode.Darken },
|
|
{ new TestPixel<RgbaVector>(), DefaultPixelBlenders<RgbaVector>.LightenSrcOver.GetType(), PixelColorBlendingMode.Lighten },
|
|
{ new TestPixel<RgbaVector>(), DefaultPixelBlenders<RgbaVector>.AddSrcOver.GetType(), PixelColorBlendingMode.Add },
|
|
{ new TestPixel<RgbaVector>(), DefaultPixelBlenders<RgbaVector>.SubtractSrcOver.GetType(), PixelColorBlendingMode.Subtract },
|
|
{ new TestPixel<RgbaVector>(), DefaultPixelBlenders<RgbaVector>.MultiplySrcOver.GetType(), PixelColorBlendingMode.Multiply },
|
|
};
|
|
|
|
public static TheoryData<PixelColorBlendingMode, PixelAlphaCompositionMode, Type> BlenderModeMappings
|
|
{
|
|
get
|
|
{
|
|
TheoryData<PixelColorBlendingMode, PixelAlphaCompositionMode, Type> data = new();
|
|
|
|
AddBlenderModeMappings(
|
|
data,
|
|
PixelAlphaCompositionMode.Clear,
|
|
DefaultPixelBlenders<Rgba32>.NormalClear.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.MultiplyClear.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.AddClear.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SubtractClear.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ScreenClear.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.DarkenClear.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.LightenClear.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.OverlayClear.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.HardLightClear.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorDodgeClear.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorBurnClear.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SoftLightClear.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.DifferenceClear.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ExclusionClear.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.HueClear.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SaturationClear.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorClear.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.LuminosityClear.GetType());
|
|
|
|
AddBlenderModeMappings(
|
|
data,
|
|
PixelAlphaCompositionMode.Xor,
|
|
DefaultPixelBlenders<Rgba32>.NormalXor.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.MultiplyXor.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.AddXor.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SubtractXor.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ScreenXor.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.DarkenXor.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.LightenXor.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.OverlayXor.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.HardLightXor.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorDodgeXor.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorBurnXor.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SoftLightXor.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.DifferenceXor.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ExclusionXor.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.HueXor.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SaturationXor.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorXor.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.LuminosityXor.GetType());
|
|
|
|
AddBlenderModeMappings(
|
|
data,
|
|
PixelAlphaCompositionMode.Plus,
|
|
DefaultPixelBlenders<Rgba32>.NormalPlus.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.MultiplyPlus.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.AddPlus.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SubtractPlus.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ScreenPlus.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.DarkenPlus.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.LightenPlus.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.OverlayPlus.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.HardLightPlus.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorDodgePlus.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorBurnPlus.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SoftLightPlus.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.DifferencePlus.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ExclusionPlus.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.HuePlus.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SaturationPlus.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorPlus.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.LuminosityPlus.GetType());
|
|
|
|
AddBlenderModeMappings(
|
|
data,
|
|
PixelAlphaCompositionMode.Src,
|
|
DefaultPixelBlenders<Rgba32>.NormalSrc.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.MultiplySrc.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.AddSrc.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SubtractSrc.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ScreenSrc.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.DarkenSrc.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.LightenSrc.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.OverlaySrc.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.HardLightSrc.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorDodgeSrc.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorBurnSrc.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SoftLightSrc.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.DifferenceSrc.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ExclusionSrc.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.HueSrc.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SaturationSrc.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorSrc.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.LuminositySrc.GetType());
|
|
|
|
AddBlenderModeMappings(
|
|
data,
|
|
PixelAlphaCompositionMode.SrcAtop,
|
|
DefaultPixelBlenders<Rgba32>.NormalSrcAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.MultiplySrcAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.AddSrcAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SubtractSrcAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ScreenSrcAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.DarkenSrcAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.LightenSrcAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.OverlaySrcAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.HardLightSrcAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorDodgeSrcAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorBurnSrcAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SoftLightSrcAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.DifferenceSrcAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ExclusionSrcAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.HueSrcAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SaturationSrcAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorSrcAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.LuminositySrcAtop.GetType());
|
|
|
|
AddBlenderModeMappings(
|
|
data,
|
|
PixelAlphaCompositionMode.SrcIn,
|
|
DefaultPixelBlenders<Rgba32>.NormalSrcIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.MultiplySrcIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.AddSrcIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SubtractSrcIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ScreenSrcIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.DarkenSrcIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.LightenSrcIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.OverlaySrcIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.HardLightSrcIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorDodgeSrcIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorBurnSrcIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SoftLightSrcIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.DifferenceSrcIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ExclusionSrcIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.HueSrcIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SaturationSrcIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorSrcIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.LuminositySrcIn.GetType());
|
|
|
|
AddBlenderModeMappings(
|
|
data,
|
|
PixelAlphaCompositionMode.SrcOut,
|
|
DefaultPixelBlenders<Rgba32>.NormalSrcOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.MultiplySrcOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.AddSrcOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SubtractSrcOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ScreenSrcOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.DarkenSrcOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.LightenSrcOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.OverlaySrcOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.HardLightSrcOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorDodgeSrcOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorBurnSrcOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SoftLightSrcOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.DifferenceSrcOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ExclusionSrcOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.HueSrcOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SaturationSrcOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorSrcOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.LuminositySrcOut.GetType());
|
|
|
|
AddBlenderModeMappings(
|
|
data,
|
|
PixelAlphaCompositionMode.Dest,
|
|
DefaultPixelBlenders<Rgba32>.NormalDest.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.MultiplyDest.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.AddDest.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SubtractDest.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ScreenDest.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.DarkenDest.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.LightenDest.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.OverlayDest.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.HardLightDest.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorDodgeDest.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorBurnDest.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SoftLightDest.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.DifferenceDest.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ExclusionDest.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.HueDest.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SaturationDest.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorDest.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.LuminosityDest.GetType());
|
|
|
|
AddBlenderModeMappings(
|
|
data,
|
|
PixelAlphaCompositionMode.DestAtop,
|
|
DefaultPixelBlenders<Rgba32>.NormalDestAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.MultiplyDestAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.AddDestAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SubtractDestAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ScreenDestAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.DarkenDestAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.LightenDestAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.OverlayDestAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.HardLightDestAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorDodgeDestAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorBurnDestAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SoftLightDestAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.DifferenceDestAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ExclusionDestAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.HueDestAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SaturationDestAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorDestAtop.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.LuminosityDestAtop.GetType());
|
|
|
|
AddBlenderModeMappings(
|
|
data,
|
|
PixelAlphaCompositionMode.DestIn,
|
|
DefaultPixelBlenders<Rgba32>.NormalDestIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.MultiplyDestIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.AddDestIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SubtractDestIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ScreenDestIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.DarkenDestIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.LightenDestIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.OverlayDestIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.HardLightDestIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorDodgeDestIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorBurnDestIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SoftLightDestIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.DifferenceDestIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ExclusionDestIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.HueDestIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SaturationDestIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorDestIn.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.LuminosityDestIn.GetType());
|
|
|
|
AddBlenderModeMappings(
|
|
data,
|
|
PixelAlphaCompositionMode.DestOut,
|
|
DefaultPixelBlenders<Rgba32>.NormalDestOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.MultiplyDestOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.AddDestOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SubtractDestOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ScreenDestOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.DarkenDestOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.LightenDestOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.OverlayDestOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.HardLightDestOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorDodgeDestOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorBurnDestOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SoftLightDestOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.DifferenceDestOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ExclusionDestOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.HueDestOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SaturationDestOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorDestOut.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.LuminosityDestOut.GetType());
|
|
|
|
AddBlenderModeMappings(
|
|
data,
|
|
PixelAlphaCompositionMode.DestOver,
|
|
DefaultPixelBlenders<Rgba32>.NormalDestOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.MultiplyDestOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.AddDestOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SubtractDestOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ScreenDestOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.DarkenDestOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.LightenDestOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.OverlayDestOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.HardLightDestOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorDodgeDestOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorBurnDestOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SoftLightDestOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.DifferenceDestOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ExclusionDestOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.HueDestOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SaturationDestOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorDestOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.LuminosityDestOver.GetType());
|
|
|
|
AddBlenderModeMappings(
|
|
data,
|
|
PixelAlphaCompositionMode.SrcOver,
|
|
DefaultPixelBlenders<Rgba32>.NormalSrcOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.MultiplySrcOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.AddSrcOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SubtractSrcOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ScreenSrcOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.DarkenSrcOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.LightenSrcOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.OverlaySrcOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.HardLightSrcOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorDodgeSrcOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorBurnSrcOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SoftLightSrcOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.DifferenceSrcOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ExclusionSrcOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.HueSrcOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.SaturationSrcOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.ColorSrcOver.GetType(),
|
|
DefaultPixelBlenders<Rgba32>.LuminositySrcOver.GetType());
|
|
|
|
return data;
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(BlenderMappings))]
|
|
public void ReturnsCorrectBlender<TPixel>(TestPixel<TPixel> pixel, Type type, PixelColorBlendingMode mode)
|
|
where TPixel : unmanaged, IPixel<TPixel>
|
|
{
|
|
PixelBlender<TPixel> blender = PixelOperations<TPixel>.Instance.GetPixelBlender(mode, PixelAlphaCompositionMode.SrcOver);
|
|
Assert.IsType(type, blender);
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(BlenderModeMappings))]
|
|
public void ReturnsCorrectBlenderForAllModeCombinations(PixelColorBlendingMode colorMode, PixelAlphaCompositionMode alphaMode, Type type)
|
|
{
|
|
PixelBlender<Rgba32> blender = PixelOperations<Rgba32>.Instance.GetPixelBlender(colorMode, alphaMode);
|
|
Assert.IsType(type, blender);
|
|
}
|
|
|
|
[Fact]
|
|
public void BlendFunctionsAreCalledForAllModeCombinations() =>
|
|
FeatureTestRunner.RunWithHwIntrinsicsFeature(
|
|
ExerciseAllBlenderModeCombinations,
|
|
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic);
|
|
|
|
[Fact]
|
|
public void AssociatedAlphaBlendFunctionsAreCalledForAllModeCombinations() =>
|
|
FeatureTestRunner.RunWithHwIntrinsicsFeature(
|
|
ExerciseAllAssociatedAlphaBlenderModeCombinations,
|
|
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic);
|
|
|
|
[Theory]
|
|
[InlineData(PixelColorBlendingMode.ColorDodge)]
|
|
[InlineData(PixelColorBlendingMode.ColorBurn)]
|
|
[InlineData(PixelColorBlendingMode.SoftLight)]
|
|
[InlineData(PixelColorBlendingMode.Difference)]
|
|
[InlineData(PixelColorBlendingMode.Exclusion)]
|
|
[InlineData(PixelColorBlendingMode.Hue)]
|
|
[InlineData(PixelColorBlendingMode.Saturation)]
|
|
[InlineData(PixelColorBlendingMode.Color)]
|
|
[InlineData(PixelColorBlendingMode.Luminosity)]
|
|
public void NewSrcOverModesMatchAcrossAlphaRepresentations(PixelColorBlendingMode colorMode)
|
|
{
|
|
Rgba32 background = new(220, 80, 40, 160);
|
|
Rgba32 source = new(20, 180, 120, 96);
|
|
PixelBlender<Rgba32> straightBlender = PixelOperations<Rgba32>.Instance.GetPixelBlender(colorMode, PixelAlphaCompositionMode.SrcOver);
|
|
PixelBlender<Rgba32P> associatedBlender = PixelOperations<Rgba32P>.Instance.GetPixelBlender(colorMode, PixelAlphaCompositionMode.SrcOver);
|
|
|
|
Rgba32 straightResult = straightBlender.Blend(background, source, .625F);
|
|
Rgba32P expected = Rgba32P.FromRgba32(straightResult);
|
|
Rgba32P actual = associatedBlender.Blend(Rgba32P.FromRgba32(background), Rgba32P.FromRgba32(source), .625F);
|
|
|
|
// Associated storage introduces one extra byte quantization, but both paths must describe the same composite.
|
|
Assert.True(Math.Abs(expected.R - actual.R) <= 1, $"{colorMode}: expected {expected}, actual {actual}");
|
|
Assert.True(Math.Abs(expected.G - actual.G) <= 1, $"{colorMode}: expected {expected}, actual {actual}");
|
|
Assert.True(Math.Abs(expected.B - actual.B) <= 1, $"{colorMode}: expected {expected}, actual {actual}");
|
|
Assert.Equal(expected.A, actual.A);
|
|
}
|
|
|
|
[Fact]
|
|
public void PlusMatchesAcrossAlphaRepresentations()
|
|
{
|
|
Rgba32 background = new(220, 80, 40, 160);
|
|
Rgba32 source = new(20, 180, 120, 96);
|
|
PixelBlender<Rgba32> straightBlender = PixelOperations<Rgba32>.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.Plus);
|
|
PixelBlender<Rgba32P> associatedBlender = PixelOperations<Rgba32P>.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.Plus);
|
|
|
|
Rgba32P expected = Rgba32P.FromRgba32(straightBlender.Blend(background, source, .625F));
|
|
Rgba32P actual = associatedBlender.Blend(Rgba32P.FromRgba32(background), Rgba32P.FromRgba32(source), .625F);
|
|
|
|
Assert.True(Math.Abs(expected.R - actual.R) <= 1, $"expected {expected}, actual {actual}");
|
|
Assert.True(Math.Abs(expected.G - actual.G) <= 1, $"expected {expected}, actual {actual}");
|
|
Assert.True(Math.Abs(expected.B - actual.B) <= 1, $"expected {expected}, actual {actual}");
|
|
Assert.Equal(expected.A, actual.A);
|
|
}
|
|
|
|
[Fact]
|
|
public void AssociatedHardLightDestAtopRoundsExactMidpointAwayFromZero() =>
|
|
FeatureTestRunner.RunWithHwIntrinsicsFeature(
|
|
RunAssociatedHardLightDestAtopRoundsExactMidpointAwayFromZero,
|
|
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic);
|
|
|
|
private static void RunAssociatedHardLightDestAtopRoundsExactMidpointAwayFromZero()
|
|
{
|
|
Rgba32P background = Rgba32P.FromRgba32(new Rgba32(220, 80, 40, 160));
|
|
Rgba32P source = Rgba32P.FromRgba32(new Rgba32(20, 180, 120, 96));
|
|
PixelBlender<Rgba32P> blender = PixelOperations<Rgba32P>.Instance.GetPixelBlender(PixelColorBlendingMode.HardLight, PixelAlphaCompositionMode.DestAtop);
|
|
|
|
Assert.Equal(new Rgba32P(30, 33, 16, 60), blender.Blend(background, source, .625F));
|
|
}
|
|
|
|
[Fact]
|
|
public void AssociatedBlendWithCoverageAppliesCoverageToColorAndAlpha() =>
|
|
FeatureTestRunner.RunWithHwIntrinsicsFeature(
|
|
RunAssociatedBlendWithCoverageAppliesCoverageToColorAndAlpha,
|
|
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic);
|
|
|
|
[Fact]
|
|
public void BlendWithCoverageMatchesAcrossAlphaRepresentations() =>
|
|
FeatureTestRunner.RunWithHwIntrinsicsFeature(
|
|
RunBlendWithCoverageMatchesAcrossAlphaRepresentations,
|
|
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic);
|
|
|
|
private static void RunAssociatedBlendWithCoverageAppliesCoverageToColorAndAlpha()
|
|
{
|
|
PixelBlender<Rgba32P> blender = PixelOperations<Rgba32P>.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver);
|
|
Rgba32P source = Rgba32P.FromRgba32(new Rgba32(37, 211, 89, 173));
|
|
Vector4 sourceScaled = source.ToScaledVector4();
|
|
|
|
// Seven pixels exercise one AVX-512 batch plus three tails, or three AVX2 batches plus one tail.
|
|
float[] coverage = [1F, .8F, .6F, .4F, .2F, 0F, .5F];
|
|
float[] amounts = [1F, 1F, 1F, 1F, 1F, 1F, 1F];
|
|
Rgba32P[] background = new Rgba32P[7];
|
|
Rgba32P[] sourceSpan = [source, source, source, source, source, source, source];
|
|
Rgba32P[] destination = new Rgba32P[7];
|
|
Rgba32P[] expected = new Rgba32P[7];
|
|
Vector4[] sourceSpanBuffer = new Vector4[destination.Length * 3];
|
|
Vector4[] constantSourceBuffer = new Vector4[destination.Length * 2];
|
|
|
|
// Compositing over transparency reduces source-over to the source itself, so coverage must
|
|
// scale the associated color and alpha lanes together: the premultiplied source times coverage.
|
|
// An unassociated coverage lerp instead premultiplies and unassociates around the mix, which
|
|
// cancels coverage out of the color lanes entirely at zero backdrop alpha.
|
|
for (int i = 0; i < expected.Length; i++)
|
|
{
|
|
expected[i] = Rgba32P.FromScaledVector4(sourceScaled * coverage[i]);
|
|
}
|
|
|
|
blender.BlendWithCoverage<Rgba32P>(Configuration.Default, destination, background, sourceSpan, 1F, coverage, sourceSpanBuffer);
|
|
Assert.Equal(expected, destination);
|
|
|
|
blender.BlendWithCoverage(Configuration.Default, destination, background, source, 1F, coverage, constantSourceBuffer);
|
|
Assert.Equal(expected, destination);
|
|
|
|
blender.BlendWithCoverage<Rgba32P>(Configuration.Default, destination, background, sourceSpan, amounts, coverage, sourceSpanBuffer);
|
|
Assert.Equal(expected, destination);
|
|
|
|
blender.BlendWithCoverage(Configuration.Default, destination, background, source, amounts, coverage, constantSourceBuffer);
|
|
Assert.Equal(expected, destination);
|
|
}
|
|
|
|
private static void RunBlendWithCoverageMatchesAcrossAlphaRepresentations()
|
|
{
|
|
PixelBlender<Rgba32> straightBlender = PixelOperations<Rgba32>.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver);
|
|
PixelBlender<Rgba32P> associatedBlender = PixelOperations<Rgba32P>.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver);
|
|
|
|
Rgba32[] background =
|
|
[
|
|
new(0, 0, 0, 0),
|
|
new(29, 83, 137, 107),
|
|
new(255, 255, 255, 255),
|
|
new(220, 80, 40, 160),
|
|
new(10, 20, 30, 40),
|
|
new(40, 200, 100, 192),
|
|
new(180, 160, 20, 128),
|
|
];
|
|
|
|
Rgba32 source = new(37, 211, 89, 173);
|
|
float[] coverage = [1F, .8F, .6F, .4F, .2F, 0F, .5F];
|
|
|
|
Rgba32[] straightDestination = new Rgba32[background.Length];
|
|
Rgba32P[] associatedDestination = new Rgba32P[background.Length];
|
|
Rgba32P[] associatedBackground = new Rgba32P[background.Length];
|
|
Vector4[] workingBuffer = new Vector4[background.Length * 2];
|
|
|
|
for (int i = 0; i < background.Length; i++)
|
|
{
|
|
associatedBackground[i] = Rgba32P.FromRgba32(background[i]);
|
|
}
|
|
|
|
straightBlender.BlendWithCoverage(Configuration.Default, straightDestination, background, source, 1F, coverage, workingBuffer);
|
|
associatedBlender.BlendWithCoverage(Configuration.Default, associatedDestination, associatedBackground, Rgba32P.FromRgba32(source), 1F, coverage, workingBuffer);
|
|
|
|
// Both destinations must describe the same composite: canonical associated bytes may differ
|
|
// by one quantization step per channel while alpha is representation-independent.
|
|
for (int i = 0; i < background.Length; i++)
|
|
{
|
|
Rgba32P expected = Rgba32P.FromRgba32(straightDestination[i]);
|
|
Rgba32P actual = associatedDestination[i];
|
|
|
|
Assert.True(Math.Abs(expected.R - actual.R) <= 1, $"[{i}] expected {expected}, actual {actual}");
|
|
Assert.True(Math.Abs(expected.G - actual.G) <= 1, $"[{i}] expected {expected}, actual {actual}");
|
|
Assert.True(Math.Abs(expected.B - actual.B) <= 1, $"[{i}] expected {expected}, actual {actual}");
|
|
Assert.Equal(expected.A, actual.A);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Blend_WithConstantSourceAndSingleAmount()
|
|
{
|
|
PixelBlender<Rgba32> blender = DefaultPixelBlenders<Rgba32>.NormalSrcOver;
|
|
Rgba32[] destination = new Rgba32[2];
|
|
Rgba32[] background =
|
|
[
|
|
Color.Red.ToPixel<Rgba32>(),
|
|
Color.Green.ToPixel<Rgba32>()
|
|
];
|
|
|
|
Rgba32 source = Color.Blue.ToPixel<Rgba32>();
|
|
|
|
blender.Blend(Configuration.Default, destination, background, source, 1F);
|
|
|
|
Assert.Equal(source, destination[0]);
|
|
Assert.Equal(source, destination[1]);
|
|
}
|
|
|
|
[Fact]
|
|
public void Blend_WithConstantSourceSingleAmountAndWorkingBuffer()
|
|
{
|
|
PixelBlender<Rgba32> blender = DefaultPixelBlenders<Rgba32>.NormalSrcOver;
|
|
Rgba32[] destination = new Rgba32[2];
|
|
Rgba32[] background =
|
|
[
|
|
Color.Red.ToPixel<Rgba32>(),
|
|
Color.Green.ToPixel<Rgba32>()
|
|
];
|
|
|
|
Rgba32 source = Color.Blue.ToPixel<Rgba32>();
|
|
Vector4[] workingBuffer = new Vector4[destination.Length * 2];
|
|
|
|
blender.Blend(Configuration.Default, destination, background, source, 1F, workingBuffer);
|
|
|
|
Assert.Equal(source, destination[0]);
|
|
Assert.Equal(source, destination[1]);
|
|
}
|
|
|
|
[Fact]
|
|
public void Blend_WithConstantSourceAndAmountSpan()
|
|
{
|
|
PixelBlender<Rgba32> blender = DefaultPixelBlenders<Rgba32>.NormalSrcOver;
|
|
Rgba32[] destination = new Rgba32[2];
|
|
Rgba32[] background =
|
|
[
|
|
Color.Red.ToPixel<Rgba32>(),
|
|
Color.Green.ToPixel<Rgba32>()
|
|
];
|
|
|
|
Rgba32 source = Color.Blue.ToPixel<Rgba32>();
|
|
float[] amount = [1F, 1F];
|
|
|
|
blender.Blend(Configuration.Default, destination, background, source, amount);
|
|
|
|
Assert.Equal(source, destination[0]);
|
|
Assert.Equal(source, destination[1]);
|
|
}
|
|
|
|
[Fact]
|
|
public void Blend_WithConstantSourceAmountSpanAndWorkingBuffer()
|
|
{
|
|
PixelBlender<Rgba32> blender = DefaultPixelBlenders<Rgba32>.NormalSrcOver;
|
|
Rgba32[] destination = new Rgba32[2];
|
|
Rgba32[] background =
|
|
[
|
|
Color.Red.ToPixel<Rgba32>(),
|
|
Color.Green.ToPixel<Rgba32>()
|
|
];
|
|
|
|
Rgba32 source = Color.Blue.ToPixel<Rgba32>();
|
|
float[] amount = [1F, 1F];
|
|
Vector4[] workingBuffer = new Vector4[destination.Length * 2];
|
|
|
|
blender.Blend(Configuration.Default, destination, background, source, amount, workingBuffer);
|
|
|
|
Assert.Equal(source, destination[0]);
|
|
Assert.Equal(source, destination[1]);
|
|
}
|
|
|
|
[Fact]
|
|
public void Blend_WithSourceSpanAmountSpanAndWorkingBuffer()
|
|
{
|
|
PixelBlender<Rgba32> blender = DefaultPixelBlenders<Rgba32>.NormalSrcOver;
|
|
Rgba32[] destination = new Rgba32[2];
|
|
Rgba32[] background =
|
|
[
|
|
Color.Red.ToPixel<Rgba32>(),
|
|
Color.Green.ToPixel<Rgba32>()
|
|
];
|
|
|
|
Rgba32[] source =
|
|
[
|
|
Color.Blue.ToPixel<Rgba32>(),
|
|
Color.Yellow.ToPixel<Rgba32>()
|
|
];
|
|
|
|
float[] amount = [1F, 1F];
|
|
Vector4[] workingBuffer = new Vector4[destination.Length * 3];
|
|
|
|
blender.Blend(Configuration.Default, destination, background, source, amount, workingBuffer);
|
|
|
|
Assert.Equal(source[0], destination[0]);
|
|
Assert.Equal(source[1], destination[1]);
|
|
}
|
|
|
|
[Fact]
|
|
public void BlendWithCoverage_WithConstantSourceAndSingleAmount()
|
|
{
|
|
PixelBlender<Rgba32> blender = DefaultPixelBlenders<Rgba32>.NormalSrc;
|
|
Rgba32[] destination = new Rgba32[3];
|
|
Rgba32[] background =
|
|
[
|
|
new(255, 0, 0),
|
|
new(255, 0, 0),
|
|
new(255, 0, 0)
|
|
];
|
|
|
|
Rgba32 source = new(0, 0, 255);
|
|
float[] coverage = [0F, .5F, 1F];
|
|
|
|
blender.BlendWithCoverage(Configuration.Default, destination, background, source, 1F, coverage);
|
|
|
|
Assert.Equal(background[0], destination[0]);
|
|
Assert.Equal(new Rgba32(128, 0, 128), destination[1]);
|
|
Assert.Equal(source, destination[2]);
|
|
}
|
|
|
|
[Fact]
|
|
public void BlendWithCoverage_WithConstantSourceSingleAmountAndWorkingBuffer()
|
|
{
|
|
PixelBlender<Rgba32> blender = DefaultPixelBlenders<Rgba32>.NormalSrc;
|
|
Rgba32[] destination = new Rgba32[3];
|
|
Rgba32[] background =
|
|
[
|
|
new(255, 0, 0),
|
|
new(255, 0, 0),
|
|
new(255, 0, 0)
|
|
];
|
|
|
|
Rgba32 source = new(0, 0, 255);
|
|
float[] coverage = [0F, .5F, 1F];
|
|
Vector4[] workingBuffer = new Vector4[destination.Length * 2];
|
|
|
|
blender.BlendWithCoverage(Configuration.Default, destination, background, source, 1F, coverage, workingBuffer);
|
|
|
|
Assert.Equal(background[0], destination[0]);
|
|
Assert.Equal(new Rgba32(128, 0, 128), destination[1]);
|
|
Assert.Equal(source, destination[2]);
|
|
}
|
|
|
|
[Fact]
|
|
public void BlendWithCoverage_WithSourceSpanAndSingleAmount()
|
|
{
|
|
PixelBlender<Rgba32> blender = DefaultPixelBlenders<Rgba32>.NormalSrc;
|
|
Rgba32[] destination = new Rgba32[3];
|
|
Rgba32[] background =
|
|
[
|
|
new(255, 0, 0),
|
|
new(0, 255, 0),
|
|
new(0, 0, 255)
|
|
];
|
|
|
|
Rgba32[] source =
|
|
[
|
|
new(0, 0, 255),
|
|
new(255, 0, 0),
|
|
new(0, 255, 0)
|
|
];
|
|
|
|
float[] coverage = [0F, .5F, 1F];
|
|
|
|
blender.BlendWithCoverage<Rgba32>(Configuration.Default, destination, background, source, 1F, coverage);
|
|
|
|
Assert.Equal(background[0], destination[0]);
|
|
Assert.Equal(new Rgba32(128, 128, 0), destination[1]);
|
|
Assert.Equal(source[2], destination[2]);
|
|
}
|
|
|
|
[Fact]
|
|
public void BlendWithCoverage_WithSourceSpanSingleAmountAndWorkingBuffer()
|
|
{
|
|
PixelBlender<Rgba32> blender = DefaultPixelBlenders<Rgba32>.NormalSrc;
|
|
Rgba32[] destination = new Rgba32[3];
|
|
Rgba32[] background =
|
|
[
|
|
new(255, 0, 0),
|
|
new(0, 255, 0),
|
|
new(0, 0, 255)
|
|
];
|
|
|
|
Rgba32[] source =
|
|
[
|
|
new(0, 0, 255),
|
|
new(255, 0, 0),
|
|
new(0, 255, 0)
|
|
];
|
|
|
|
float[] coverage = [0F, .5F, 1F];
|
|
Vector4[] workingBuffer = new Vector4[destination.Length * 3];
|
|
|
|
blender.BlendWithCoverage<Rgba32>(Configuration.Default, destination, background, source, 1F, coverage, workingBuffer);
|
|
|
|
Assert.Equal(background[0], destination[0]);
|
|
Assert.Equal(new Rgba32(128, 128, 0), destination[1]);
|
|
Assert.Equal(source[2], destination[2]);
|
|
}
|
|
|
|
[Fact]
|
|
public void BlendWithCoverage_WithConstantSourceAndAmountSpan()
|
|
{
|
|
PixelBlender<Rgba32> blender = DefaultPixelBlenders<Rgba32>.NormalSrc;
|
|
Rgba32[] destination = new Rgba32[3];
|
|
Rgba32[] background =
|
|
[
|
|
new(255, 0, 0),
|
|
new(255, 0, 0),
|
|
new(255, 0, 0)
|
|
];
|
|
|
|
Rgba32 source = new(0, 0, 255);
|
|
float[] amount = [1F, 1F, 1F];
|
|
float[] coverage = [0F, .5F, 1F];
|
|
|
|
blender.BlendWithCoverage(Configuration.Default, destination, background, source, amount, coverage);
|
|
|
|
Assert.Equal(background[0], destination[0]);
|
|
Assert.Equal(new Rgba32(128, 0, 128), destination[1]);
|
|
Assert.Equal(source, destination[2]);
|
|
}
|
|
|
|
[Fact]
|
|
public void BlendWithCoverage_WithConstantSourceAmountSpanAndWorkingBuffer()
|
|
{
|
|
PixelBlender<Rgba32> blender = DefaultPixelBlenders<Rgba32>.NormalSrc;
|
|
Rgba32[] destination = new Rgba32[3];
|
|
Rgba32[] background =
|
|
[
|
|
new(255, 0, 0),
|
|
new(255, 0, 0),
|
|
new(255, 0, 0)
|
|
];
|
|
|
|
Rgba32 source = new(0, 0, 255);
|
|
float[] amount = [1F, 1F, 1F];
|
|
float[] coverage = [0F, .5F, 1F];
|
|
Vector4[] workingBuffer = new Vector4[destination.Length * 2];
|
|
|
|
blender.BlendWithCoverage(Configuration.Default, destination, background, source, amount, coverage, workingBuffer);
|
|
|
|
Assert.Equal(background[0], destination[0]);
|
|
Assert.Equal(new Rgba32(128, 0, 128), destination[1]);
|
|
Assert.Equal(source, destination[2]);
|
|
}
|
|
|
|
[Fact]
|
|
public void BlendWithCoverage_WithSourceSpanAndAmountSpan()
|
|
{
|
|
PixelBlender<Rgba32> blender = DefaultPixelBlenders<Rgba32>.NormalSrc;
|
|
Rgba32[] destination = new Rgba32[3];
|
|
Rgba32[] background =
|
|
[
|
|
new(255, 0, 0),
|
|
new(0, 255, 0),
|
|
new(0, 0, 255)
|
|
];
|
|
|
|
Rgba32[] source =
|
|
[
|
|
new(0, 0, 255),
|
|
new(255, 0, 0),
|
|
new(0, 255, 0)
|
|
];
|
|
|
|
float[] amount = [1F, 1F, 1F];
|
|
float[] coverage = [0F, .5F, 1F];
|
|
|
|
blender.BlendWithCoverage<Rgba32>(Configuration.Default, destination, background, source, amount, coverage);
|
|
|
|
Assert.Equal(background[0], destination[0]);
|
|
Assert.Equal(new Rgba32(128, 128, 0), destination[1]);
|
|
Assert.Equal(source[2], destination[2]);
|
|
}
|
|
|
|
[Fact]
|
|
public void BlendWithCoverage_WithSourceSpanAmountSpanAndWorkingBuffer()
|
|
{
|
|
PixelBlender<Rgba32> blender = DefaultPixelBlenders<Rgba32>.NormalSrc;
|
|
Rgba32[] destination = new Rgba32[3];
|
|
Rgba32[] background =
|
|
[
|
|
new(255, 0, 0),
|
|
new(0, 255, 0),
|
|
new(0, 0, 255)
|
|
];
|
|
|
|
Rgba32[] source =
|
|
[
|
|
new(0, 0, 255),
|
|
new(255, 0, 0),
|
|
new(0, 255, 0)
|
|
];
|
|
|
|
float[] amount = [1F, 1F, 1F];
|
|
float[] coverage = [0F, .5F, 1F];
|
|
Vector4[] workingBuffer = new Vector4[destination.Length * 3];
|
|
|
|
blender.BlendWithCoverage<Rgba32>(Configuration.Default, destination, background, source, amount, coverage, workingBuffer);
|
|
|
|
Assert.Equal(background[0], destination[0]);
|
|
Assert.Equal(new Rgba32(128, 128, 0), destination[1]);
|
|
Assert.Equal(source[2], destination[2]);
|
|
}
|
|
|
|
public static TheoryData<Rgba32, Rgba32, float, PixelColorBlendingMode, Rgba32> ColorBlendingExpectedResults = new()
|
|
{
|
|
{ Color.MistyRose.ToPixel<Rgba32>(), Color.MidnightBlue.ToPixel<Rgba32>(), 1, PixelColorBlendingMode.Normal, Color.MidnightBlue.ToPixel<Rgba32>() },
|
|
{ Color.MistyRose.ToPixel<Rgba32>(), Color.MidnightBlue.ToPixel<Rgba32>(), 1, PixelColorBlendingMode.Screen, new Rgba32(0xFFEEE7FF) },
|
|
{ Color.MistyRose.ToPixel<Rgba32>(), Color.MidnightBlue.ToPixel<Rgba32>(), 1, PixelColorBlendingMode.HardLight, new Rgba32(0xFFC62D32) },
|
|
{ Color.MistyRose.ToPixel<Rgba32>(), Color.MidnightBlue.ToPixel<Rgba32>(), 1, PixelColorBlendingMode.Overlay, new Rgba32(0xFFDDCEFF) },
|
|
{ Color.MistyRose.ToPixel<Rgba32>(), Color.MidnightBlue.ToPixel<Rgba32>(), 1, PixelColorBlendingMode.Darken, new Rgba32(0xFF701919) },
|
|
{ Color.MistyRose.ToPixel<Rgba32>(), Color.MidnightBlue.ToPixel<Rgba32>(), 1, PixelColorBlendingMode.Lighten, new Rgba32(0xFFE1E4FF) },
|
|
{ Color.MistyRose.ToPixel<Rgba32>(), Color.MidnightBlue.ToPixel<Rgba32>(), 1, PixelColorBlendingMode.Add, new Rgba32(0xFFFFFDFF) },
|
|
{ Color.MistyRose.ToPixel<Rgba32>(), Color.MidnightBlue.ToPixel<Rgba32>(), 1, PixelColorBlendingMode.Subtract, new Rgba32(0xFF71CBE6) },
|
|
{ Color.MistyRose.ToPixel<Rgba32>(), Color.MidnightBlue.ToPixel<Rgba32>(), 1, PixelColorBlendingMode.Multiply, new Rgba32(0xFF631619) },
|
|
};
|
|
|
|
[Theory]
|
|
[MemberData(nameof(ColorBlendingExpectedResults))]
|
|
public void TestColorBlendingModes(Rgba32 backdrop, Rgba32 source, float opacity, PixelColorBlendingMode mode, Rgba32 expectedResult)
|
|
{
|
|
PixelBlender<Rgba32> blender = PixelOperations<Rgba32>.Instance.GetPixelBlender(mode, PixelAlphaCompositionMode.SrcOver);
|
|
Rgba32 actualResult = blender.Blend(backdrop, source, opacity);
|
|
|
|
// var str = actualResult.Rgba.ToString("X8"); // used to extract expectedResults
|
|
Assert.Equal(actualResult.ToVector4(), expectedResult.ToVector4());
|
|
}
|
|
|
|
public static TheoryData<Rgba32, Rgba32, float, PixelAlphaCompositionMode, Rgba32> AlphaCompositionExpectedResults = new()
|
|
{
|
|
{ Color.MistyRose.ToPixel<Rgba32>(), Color.MidnightBlue.ToPixel<Rgba32>(), 1, PixelAlphaCompositionMode.Clear, new Rgba32(0) },
|
|
{ Color.MistyRose.ToPixel<Rgba32>(), Color.MidnightBlue.ToPixel<Rgba32>(), 1, PixelAlphaCompositionMode.Xor, new Rgba32(0) },
|
|
{ Color.MistyRose.ToPixel<Rgba32>(), Color.MidnightBlue.ToPixel<Rgba32>(), 1, PixelAlphaCompositionMode.Dest, Color.MistyRose.ToPixel<Rgba32>() },
|
|
{ Color.MistyRose.ToPixel<Rgba32>(), Color.MidnightBlue.ToPixel<Rgba32>(), 1, PixelAlphaCompositionMode.DestAtop, Color.MistyRose.ToPixel<Rgba32>() },
|
|
{ Color.MistyRose.ToPixel<Rgba32>(), Color.MidnightBlue.ToPixel<Rgba32>(), 1, PixelAlphaCompositionMode.DestIn, Color.MistyRose.ToPixel<Rgba32>() },
|
|
{ Color.MistyRose.ToPixel<Rgba32>(), Color.MidnightBlue.ToPixel<Rgba32>(), 1, PixelAlphaCompositionMode.DestOut, new Rgba32(0) },
|
|
{ Color.MistyRose.ToPixel<Rgba32>(), Color.MidnightBlue.ToPixel<Rgba32>(), 1, PixelAlphaCompositionMode.DestOver, Color.MistyRose.ToPixel<Rgba32>() },
|
|
{ Color.MistyRose.ToPixel<Rgba32>(), Color.MidnightBlue.ToPixel<Rgba32>(), 1, PixelAlphaCompositionMode.Src, Color.MidnightBlue.ToPixel<Rgba32>() },
|
|
{ Color.MistyRose.ToPixel<Rgba32>(), Color.MidnightBlue.ToPixel<Rgba32>(), 1, PixelAlphaCompositionMode.SrcAtop, Color.MidnightBlue.ToPixel<Rgba32>() },
|
|
{ Color.MistyRose.ToPixel<Rgba32>(), Color.MidnightBlue.ToPixel<Rgba32>(), 1, PixelAlphaCompositionMode.SrcIn, Color.MidnightBlue.ToPixel<Rgba32>() },
|
|
{ Color.MistyRose.ToPixel<Rgba32>(), Color.MidnightBlue.ToPixel<Rgba32>(), 1, PixelAlphaCompositionMode.SrcOut, new Rgba32(0) },
|
|
{ Color.MistyRose.ToPixel<Rgba32>(), Color.MidnightBlue.ToPixel<Rgba32>(), 1, PixelAlphaCompositionMode.SrcOver, Color.MidnightBlue.ToPixel<Rgba32>() },
|
|
{ new Rgba32(51, 102, 153, 77), new Rgba32(204, 51, 26, 102), .5F, PixelAlphaCompositionMode.Plus, new Rgba32(112, 82, 102, 128) },
|
|
{ new Rgba32(230, 51, 26, 191), new Rgba32(204, 230, 77, 191), 1F, PixelAlphaCompositionMode.Plus, new Rgba32(255, 210, 77, 255) },
|
|
};
|
|
|
|
[Theory]
|
|
[MemberData(nameof(AlphaCompositionExpectedResults))]
|
|
public void TestAlphaCompositionModes(Rgba32 backdrop, Rgba32 source, float opacity, PixelAlphaCompositionMode mode, Rgba32 expectedResult)
|
|
{
|
|
PixelBlender<Rgba32> blender = PixelOperations<Rgba32>.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, mode);
|
|
|
|
Rgba32 actualResult = blender.Blend(backdrop, source, opacity);
|
|
|
|
// var str = actualResult.Rgba.ToString("X8"); // used to extract expectedResults
|
|
Assert.Equal(actualResult.ToVector4(), expectedResult.ToVector4());
|
|
}
|
|
|
|
private static void AddBlenderModeMappings(
|
|
TheoryData<PixelColorBlendingMode, PixelAlphaCompositionMode, Type> data,
|
|
PixelAlphaCompositionMode alphaMode,
|
|
Type normal,
|
|
Type multiply,
|
|
Type add,
|
|
Type subtract,
|
|
Type screen,
|
|
Type darken,
|
|
Type lighten,
|
|
Type overlay,
|
|
Type hardLight,
|
|
Type colorDodge,
|
|
Type colorBurn,
|
|
Type softLight,
|
|
Type difference,
|
|
Type exclusion,
|
|
Type hue,
|
|
Type saturation,
|
|
Type color,
|
|
Type luminosity)
|
|
{
|
|
data.Add(PixelColorBlendingMode.Normal, alphaMode, normal);
|
|
data.Add(PixelColorBlendingMode.Multiply, alphaMode, multiply);
|
|
data.Add(PixelColorBlendingMode.Add, alphaMode, add);
|
|
data.Add(PixelColorBlendingMode.Subtract, alphaMode, subtract);
|
|
data.Add(PixelColorBlendingMode.Screen, alphaMode, screen);
|
|
data.Add(PixelColorBlendingMode.Darken, alphaMode, darken);
|
|
data.Add(PixelColorBlendingMode.Lighten, alphaMode, lighten);
|
|
data.Add(PixelColorBlendingMode.Overlay, alphaMode, overlay);
|
|
data.Add(PixelColorBlendingMode.HardLight, alphaMode, hardLight);
|
|
data.Add(PixelColorBlendingMode.ColorDodge, alphaMode, colorDodge);
|
|
data.Add(PixelColorBlendingMode.ColorBurn, alphaMode, colorBurn);
|
|
data.Add(PixelColorBlendingMode.SoftLight, alphaMode, softLight);
|
|
data.Add(PixelColorBlendingMode.Difference, alphaMode, difference);
|
|
data.Add(PixelColorBlendingMode.Exclusion, alphaMode, exclusion);
|
|
data.Add(PixelColorBlendingMode.Hue, alphaMode, hue);
|
|
data.Add(PixelColorBlendingMode.Saturation, alphaMode, saturation);
|
|
data.Add(PixelColorBlendingMode.Color, alphaMode, color);
|
|
data.Add(PixelColorBlendingMode.Luminosity, alphaMode, luminosity);
|
|
}
|
|
|
|
private static void ExerciseAllBlenderModeCombinations()
|
|
{
|
|
foreach (PixelAlphaCompositionMode alphaMode in Enum.GetValues<PixelAlphaCompositionMode>())
|
|
{
|
|
foreach (PixelColorBlendingMode colorMode in Enum.GetValues<PixelColorBlendingMode>())
|
|
{
|
|
PixelBlender<Rgba32> blender = PixelOperations<Rgba32>.Instance.GetPixelBlender(colorMode, alphaMode);
|
|
ExerciseBlender(blender);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void ExerciseAllAssociatedAlphaBlenderModeCombinations()
|
|
{
|
|
Rgba32P[] background =
|
|
[
|
|
Rgba32P.FromRgba32(new Rgba32(220, 80, 40, 160)),
|
|
Rgba32P.FromRgba32(new Rgba32(40, 200, 100, 192)),
|
|
Rgba32P.FromRgba32(new Rgba32(120, 60, 230, 224)),
|
|
Rgba32P.FromRgba32(new Rgba32(180, 160, 20, 128)),
|
|
Rgba32P.FromRgba32(new Rgba32(30, 140, 210, 96)),
|
|
];
|
|
|
|
Rgba32P[] source =
|
|
[
|
|
Rgba32P.FromRgba32(new Rgba32(20, 180, 120, 96)),
|
|
Rgba32P.FromRgba32(new Rgba32(210, 30, 150, 144)),
|
|
Rgba32P.FromRgba32(new Rgba32(80, 220, 40, 176)),
|
|
Rgba32P.FromRgba32(new Rgba32(240, 110, 60, 208)),
|
|
Rgba32P.FromRgba32(new Rgba32(100, 50, 200, 112)),
|
|
];
|
|
|
|
foreach (PixelAlphaCompositionMode alphaMode in Enum.GetValues<PixelAlphaCompositionMode>())
|
|
{
|
|
foreach (PixelColorBlendingMode colorMode in Enum.GetValues<PixelColorBlendingMode>())
|
|
{
|
|
PixelBlender<Rgba32P> blender = PixelOperations<Rgba32P>.Instance.GetPixelBlender(colorMode, alphaMode);
|
|
AssertAssociatedBlenderMatchesScalar(blender, background, source, colorMode, alphaMode);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void AssertAssociatedBlenderMatchesScalar(
|
|
PixelBlender<Rgba32P> blender,
|
|
Rgba32P[] associatedBackground,
|
|
Rgba32P[] associatedSource,
|
|
PixelColorBlendingMode colorMode,
|
|
PixelAlphaCompositionMode alphaMode)
|
|
{
|
|
const float amount = .625F;
|
|
float[] amounts = [.125F, .375F, .625F, .875F, 1F];
|
|
float[] coverage = [.2F, .4F, .6F, .8F, 1F];
|
|
Rgba32P constantAssociatedSource = associatedSource[2];
|
|
|
|
Rgba32P[] expected = new Rgba32P[associatedBackground.Length];
|
|
Rgba32P[] actual = new Rgba32P[associatedBackground.Length];
|
|
Vector4[] actualSourceSpanBuffer = new Vector4[associatedBackground.Length * 3];
|
|
Vector4[] actualConstantSourceBuffer = new Vector4[associatedBackground.Length * 2];
|
|
|
|
for (int i = 0; i < expected.Length; i++)
|
|
{
|
|
expected[i] = blender.Blend(associatedBackground[i], associatedSource[i], amount);
|
|
}
|
|
|
|
blender.Blend<Rgba32P>(Configuration.Default, actual, associatedBackground, associatedSource, amount, actualSourceSpanBuffer);
|
|
AssertRgba32PEqual(expected, actual, colorMode, alphaMode, "SourceSpanSingleAmount");
|
|
|
|
for (int i = 0; i < expected.Length; i++)
|
|
{
|
|
expected[i] = blender.Blend(associatedBackground[i], constantAssociatedSource, amount);
|
|
}
|
|
|
|
blender.Blend(Configuration.Default, actual, associatedBackground, constantAssociatedSource, amount, actualConstantSourceBuffer);
|
|
AssertRgba32PEqual(expected, actual, colorMode, alphaMode, "ConstantSourceSingleAmount");
|
|
|
|
for (int i = 0; i < expected.Length; i++)
|
|
{
|
|
expected[i] = blender.Blend(associatedBackground[i], associatedSource[i], amounts[i]);
|
|
}
|
|
|
|
blender.Blend<Rgba32P>(Configuration.Default, actual, associatedBackground, associatedSource, amounts, actualSourceSpanBuffer);
|
|
AssertRgba32PEqual(expected, actual, colorMode, alphaMode, "SourceSpanAmountSpan");
|
|
|
|
for (int i = 0; i < expected.Length; i++)
|
|
{
|
|
expected[i] = blender.Blend(associatedBackground[i], constantAssociatedSource, amounts[i]);
|
|
}
|
|
|
|
blender.Blend(Configuration.Default, actual, associatedBackground, constantAssociatedSource, amounts, actualConstantSourceBuffer);
|
|
AssertRgba32PEqual(expected, actual, colorMode, alphaMode, "ConstantSourceAmountSpan");
|
|
|
|
for (int i = 0; i < expected.Length; i++)
|
|
{
|
|
expected[i] = BlendWithCoverageScalar(blender, associatedBackground[i], associatedSource[i], amount, coverage[i]);
|
|
}
|
|
|
|
blender.BlendWithCoverage<Rgba32P>(Configuration.Default, actual, associatedBackground, associatedSource, amount, coverage, actualSourceSpanBuffer);
|
|
AssertRgba32PEqual(expected, actual, colorMode, alphaMode, "SourceSpanSingleAmountCoverage");
|
|
|
|
for (int i = 0; i < expected.Length; i++)
|
|
{
|
|
expected[i] = BlendWithCoverageScalar(blender, associatedBackground[i], constantAssociatedSource, amount, coverage[i]);
|
|
}
|
|
|
|
blender.BlendWithCoverage(Configuration.Default, actual, associatedBackground, constantAssociatedSource, amount, coverage, actualConstantSourceBuffer);
|
|
AssertRgba32PEqual(expected, actual, colorMode, alphaMode, "ConstantSourceSingleAmountCoverage");
|
|
|
|
for (int i = 0; i < expected.Length; i++)
|
|
{
|
|
expected[i] = BlendWithCoverageScalar(blender, associatedBackground[i], associatedSource[i], amounts[i], coverage[i]);
|
|
}
|
|
|
|
blender.BlendWithCoverage<Rgba32P>(Configuration.Default, actual, associatedBackground, associatedSource, amounts, coverage, actualSourceSpanBuffer);
|
|
AssertRgba32PEqual(expected, actual, colorMode, alphaMode, "SourceSpanAmountSpanCoverage");
|
|
|
|
for (int i = 0; i < expected.Length; i++)
|
|
{
|
|
expected[i] = BlendWithCoverageScalar(blender, associatedBackground[i], constantAssociatedSource, amounts[i], coverage[i]);
|
|
}
|
|
|
|
blender.BlendWithCoverage(Configuration.Default, actual, associatedBackground, constantAssociatedSource, amounts, coverage, actualConstantSourceBuffer);
|
|
AssertRgba32PEqual(expected, actual, colorMode, alphaMode, "ConstantSourceAmountSpanCoverage");
|
|
}
|
|
|
|
private static Rgba32P BlendWithCoverageScalar(PixelBlender<Rgba32P> blender, Rgba32P background, Rgba32P source, float amount, float coverage)
|
|
{
|
|
Span<Rgba32P> destination = stackalloc Rgba32P[1];
|
|
Span<Rgba32P> backgroundSpan = stackalloc Rgba32P[1] { background };
|
|
Span<Rgba32P> sourceSpan = stackalloc Rgba32P[1] { source };
|
|
Span<float> coverageSpan = stackalloc float[1] { coverage };
|
|
Span<Vector4> buffer = stackalloc Vector4[3];
|
|
|
|
// A one-pixel span takes the scalar remainder path, providing an exact oracle for each SIMD coverage result.
|
|
blender.BlendWithCoverage<Rgba32P>(Configuration.Default, destination, backgroundSpan, sourceSpan, amount, coverageSpan, buffer);
|
|
return destination[0];
|
|
}
|
|
|
|
private static void AssertRgba32PEqual(
|
|
ReadOnlySpan<Rgba32P> expected,
|
|
ReadOnlySpan<Rgba32P> actual,
|
|
PixelColorBlendingMode colorMode,
|
|
PixelAlphaCompositionMode alphaMode,
|
|
string scenario)
|
|
{
|
|
for (int i = 0; i < expected.Length; i++)
|
|
{
|
|
Assert.True(expected[i] == actual[i], $"{colorMode}/{alphaMode}/{scenario}[{i}]: expected {expected[i]}, actual {actual[i]}");
|
|
}
|
|
}
|
|
|
|
private static void ExerciseBlender(PixelBlender<Rgba32> blender)
|
|
{
|
|
Rgba32 background = Color.MistyRose.ToPixel<Rgba32>();
|
|
Rgba32 source = Color.MidnightBlue.ToPixel<Rgba32>();
|
|
|
|
ExerciseBlender(blender, background, source);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Exercises every bulk overload across vector-width boundaries and compares it with single-pixel composition.
|
|
/// </summary>
|
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
/// <param name="blender">The blender under test.</param>
|
|
/// <param name="background">The background pixel.</param>
|
|
/// <param name="source">The source pixel.</param>
|
|
private static void ExerciseBlender<TPixel>(PixelBlender<TPixel> blender, TPixel background, TPixel source)
|
|
where TPixel : unmanaged, IPixel<TPixel>
|
|
{
|
|
const float amount = .625F;
|
|
|
|
float[] amounts = [0F, .125F, .375F, .625F, .875F, 1F, .5F];
|
|
float[] coverage = [1F, .8F, .6F, .4F, .2F, 0F, .5F];
|
|
|
|
// Seven pixels exercise one AVX-512 batch plus three tails, or three AVX2 batches plus one tail.
|
|
TPixel[] destination = new TPixel[7];
|
|
TPixel[] expected = new TPixel[7];
|
|
TPixel[] backgroundSpan = [background, background, background, background, background, background, background];
|
|
TPixel[] sourceSpan = [source, source, source, source, source, source, source];
|
|
Vector4[] sourceSpanBuffer = new Vector4[destination.Length * 3];
|
|
Vector4[] constantSourceBuffer = new Vector4[destination.Length * 2];
|
|
|
|
Array.Fill(expected, blender.Blend(background, source, amount));
|
|
|
|
blender.Blend<TPixel>(Configuration.Default, destination, backgroundSpan, sourceSpan, amount, sourceSpanBuffer);
|
|
Assert.Equal(expected, destination);
|
|
|
|
blender.Blend(Configuration.Default, destination, backgroundSpan, source, amount, constantSourceBuffer);
|
|
Assert.Equal(expected, destination);
|
|
|
|
for (int i = 0; i < expected.Length; i++)
|
|
{
|
|
expected[i] = blender.Blend(background, source, amounts[i]);
|
|
}
|
|
|
|
blender.Blend(Configuration.Default, destination, backgroundSpan, sourceSpan, amounts, sourceSpanBuffer);
|
|
Assert.Equal(expected, destination);
|
|
|
|
blender.Blend(Configuration.Default, destination, backgroundSpan, source, amounts, constantSourceBuffer);
|
|
Assert.Equal(expected, destination);
|
|
|
|
for (int i = 0; i < expected.Length; i++)
|
|
{
|
|
expected[i] = BlendWithCoverageScalar(blender, background, source, amount, coverage[i]);
|
|
}
|
|
|
|
blender.BlendWithCoverage<TPixel>(Configuration.Default, destination, backgroundSpan, sourceSpan, amount, coverage, sourceSpanBuffer);
|
|
Assert.Equal(expected, destination);
|
|
|
|
blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, source, amount, coverage, constantSourceBuffer);
|
|
Assert.Equal(expected, destination);
|
|
|
|
for (int i = 0; i < expected.Length; i++)
|
|
{
|
|
expected[i] = BlendWithCoverageScalar(blender, background, source, amounts[i], coverage[i]);
|
|
}
|
|
|
|
blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, sourceSpan, amounts, coverage, sourceSpanBuffer);
|
|
Assert.Equal(expected, destination);
|
|
|
|
blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, source, amounts, coverage, constantSourceBuffer);
|
|
Assert.Equal(expected, destination);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Computes a one-pixel coverage result through the scalar remainder path.
|
|
/// </summary>
|
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
/// <param name="blender">The blender under test.</param>
|
|
/// <param name="background">The background pixel.</param>
|
|
/// <param name="source">The source pixel.</param>
|
|
/// <param name="amount">The source opacity.</param>
|
|
/// <param name="coverage">The pixel coverage.</param>
|
|
/// <returns>The blended pixel.</returns>
|
|
private static TPixel BlendWithCoverageScalar<TPixel>(PixelBlender<TPixel> blender, TPixel background, TPixel source, float amount, float coverage)
|
|
where TPixel : unmanaged, IPixel<TPixel>
|
|
{
|
|
Span<TPixel> destination = stackalloc TPixel[1];
|
|
Span<TPixel> backgroundSpan = stackalloc TPixel[1] { background };
|
|
Span<TPixel> sourceSpan = stackalloc TPixel[1] { source };
|
|
Span<float> coverageSpan = stackalloc float[1] { coverage };
|
|
Span<Vector4> buffer = stackalloc Vector4[3];
|
|
|
|
blender.BlendWithCoverage<TPixel>(Configuration.Default, destination, backgroundSpan, sourceSpan, amount, coverageSpan, buffer);
|
|
|
|
return destination[0];
|
|
}
|
|
}
|
|
|