|
|
|
@ -25,26 +25,31 @@ internal static class AnimationUtilities |
|
|
|
/// <param name="configuration">The configuration.</param>
|
|
|
|
/// <param name="previousFrame">The previous frame if present.</param>
|
|
|
|
/// <param name="currentFrame">The current frame.</param>
|
|
|
|
/// <param name="nextFrame">The next frame if present.</param>
|
|
|
|
/// <param name="resultFrame">The resultant output.</param>
|
|
|
|
/// <param name="replacement">The value to use when replacing duplicate pixels.</param>
|
|
|
|
/// <param name="blend">Whether the resultant frame represents an animation blend.</param>
|
|
|
|
/// <param name="clampingMode">The clamping bound to apply when calculating difference bounds.</param>
|
|
|
|
/// <returns>The <see cref="ValueTuple{Boolean, Rectangle}"/> representing the operation result.</returns>
|
|
|
|
public static (bool Difference, Rectangle Bounds) DeDuplicatePixels<TPixel>( |
|
|
|
Configuration configuration, |
|
|
|
ImageFrame<TPixel>? previousFrame, |
|
|
|
ImageFrame<TPixel> currentFrame, |
|
|
|
ImageFrame<TPixel>? nextFrame, |
|
|
|
ImageFrame<TPixel> resultFrame, |
|
|
|
Vector4 replacement, |
|
|
|
Color replacement, |
|
|
|
bool blend, |
|
|
|
ClampingMode clampingMode = ClampingMode.None) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
// TODO: This would be faster (but more complicated to find diff bounds) if we operated on Rgba32.
|
|
|
|
// If someone wants to do that, they have my unlimited thanks.
|
|
|
|
MemoryAllocator memoryAllocator = configuration.MemoryAllocator; |
|
|
|
IMemoryOwner<Vector4> buffers = memoryAllocator.Allocate<Vector4>(currentFrame.Width * 3, AllocationOptions.Clean); |
|
|
|
Span<Vector4> previous = buffers.GetSpan()[..currentFrame.Width]; |
|
|
|
Span<Vector4> current = buffers.GetSpan().Slice(currentFrame.Width, currentFrame.Width); |
|
|
|
Span<Vector4> result = buffers.GetSpan()[(currentFrame.Width * 2)..]; |
|
|
|
IMemoryOwner<Rgba32> buffers = memoryAllocator.Allocate<Rgba32>(currentFrame.Width * 4, AllocationOptions.Clean); |
|
|
|
Span<Rgba32> previous = buffers.GetSpan()[..currentFrame.Width]; |
|
|
|
Span<Rgba32> current = buffers.GetSpan().Slice(currentFrame.Width, currentFrame.Width); |
|
|
|
Span<Rgba32> next = buffers.GetSpan().Slice(currentFrame.Width * 2, currentFrame.Width); |
|
|
|
Span<Rgba32> result = buffers.GetSpan()[(currentFrame.Width * 3)..]; |
|
|
|
|
|
|
|
Rgba32 bg = replacement; |
|
|
|
|
|
|
|
int top = int.MinValue; |
|
|
|
int bottom = int.MaxValue; |
|
|
|
@ -56,70 +61,90 @@ internal static class AnimationUtilities |
|
|
|
{ |
|
|
|
if (previousFrame != null) |
|
|
|
{ |
|
|
|
PixelOperations<TPixel>.Instance.ToVector4(configuration, previousFrame.DangerousGetPixelRowMemory(y).Span, previous, PixelConversionModifiers.Scale); |
|
|
|
PixelOperations<TPixel>.Instance.ToRgba32(configuration, previousFrame.DangerousGetPixelRowMemory(y).Span, previous); |
|
|
|
} |
|
|
|
|
|
|
|
PixelOperations<TPixel>.Instance.ToVector4(configuration, currentFrame.DangerousGetPixelRowMemory(y).Span, current, PixelConversionModifiers.Scale); |
|
|
|
|
|
|
|
ref Vector256<float> previousBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(previous)); |
|
|
|
ref Vector256<float> currentBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(current)); |
|
|
|
ref Vector256<float> resultBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(result)); |
|
|
|
PixelOperations<TPixel>.Instance.ToRgba32(configuration, currentFrame.DangerousGetPixelRowMemory(y).Span, current); |
|
|
|
|
|
|
|
Vector256<float> replacement256 = Vector256.Create(replacement.X, replacement.Y, replacement.Z, replacement.W, replacement.X, replacement.Y, replacement.Z, replacement.W); |
|
|
|
if (nextFrame != null) |
|
|
|
{ |
|
|
|
PixelOperations<TPixel>.Instance.ToRgba32(configuration, nextFrame.DangerousGetPixelRowMemory(y).Span, next); |
|
|
|
} |
|
|
|
|
|
|
|
int size = Unsafe.SizeOf<Vector4>(); |
|
|
|
ref Vector256<byte> previousBase = ref Unsafe.As<Rgba32, Vector256<byte>>(ref MemoryMarshal.GetReference(previous)); |
|
|
|
ref Vector256<byte> currentBase = ref Unsafe.As<Rgba32, Vector256<byte>>(ref MemoryMarshal.GetReference(current)); |
|
|
|
ref Vector256<byte> nextBase = ref Unsafe.As<Rgba32, Vector256<byte>>(ref MemoryMarshal.GetReference(next)); |
|
|
|
ref Vector256<byte> resultBase = ref Unsafe.As<Rgba32, Vector256<byte>>(ref MemoryMarshal.GetReference(result)); |
|
|
|
|
|
|
|
bool hasRowDiff = false; |
|
|
|
int i = 0; |
|
|
|
uint x = 0; |
|
|
|
bool hasRowDiff = false; |
|
|
|
int length = current.Length; |
|
|
|
int remaining = current.Length; |
|
|
|
|
|
|
|
while (Avx2.IsSupported && remaining >= 2) |
|
|
|
if (Avx2.IsSupported && remaining >= 8) |
|
|
|
{ |
|
|
|
Vector256<float> p = Unsafe.Add(ref previousBase, x); |
|
|
|
Vector256<float> c = Unsafe.Add(ref currentBase, x); |
|
|
|
|
|
|
|
// Compare the previous and current pixels
|
|
|
|
Vector256<int> mask = Avx2.CompareEqual(p.AsInt32(), c.AsInt32()); |
|
|
|
mask = Avx2.CompareEqual(mask.AsInt64(), Vector256<long>.AllBitsSet).AsInt32(); |
|
|
|
mask = Avx2.And(mask, Avx2.Shuffle(mask, 0b_01_00_11_10)).AsInt32(); |
|
|
|
|
|
|
|
Vector256<int> neq = Avx2.Xor(mask.AsInt64(), Vector256<long>.AllBitsSet).AsInt32(); |
|
|
|
int m = Avx2.MoveMask(neq.AsByte()); |
|
|
|
if (m != 0) |
|
|
|
Vector256<uint> r256 = previousFrame != null ? Vector256.Create(bg.PackedValue) : Vector256<uint>.Zero; |
|
|
|
Vector256<uint> vmb256 = Vector256<uint>.Zero; |
|
|
|
if (blend) |
|
|
|
{ |
|
|
|
// If is diff is found, the left side is marked by the min of previously found left side and the start position.
|
|
|
|
// The right is the max of the previously found right side and the end position.
|
|
|
|
int start = i + (BitOperations.TrailingZeroCount(m) / size); |
|
|
|
int end = i + (2 - (BitOperations.LeadingZeroCount((uint)m) / size)); |
|
|
|
left = Math.Min(left, start); |
|
|
|
right = Math.Max(right, end); |
|
|
|
hasRowDiff = true; |
|
|
|
hasDiff = true; |
|
|
|
vmb256 = Avx2.CompareEqual(vmb256, vmb256); |
|
|
|
} |
|
|
|
|
|
|
|
// Replace the pixel value with the replacement if the full pixel is matched.
|
|
|
|
Vector256<float> r = Avx.BlendVariable(c, replacement256, mask.AsSingle()); |
|
|
|
Unsafe.Add(ref resultBase, x) = r; |
|
|
|
|
|
|
|
x++; |
|
|
|
i += 2; |
|
|
|
remaining -= 2; |
|
|
|
while (remaining >= 8) |
|
|
|
{ |
|
|
|
Vector256<uint> p = Unsafe.Add(ref previousBase, x).AsUInt32(); |
|
|
|
Vector256<uint> c = Unsafe.Add(ref currentBase, x).AsUInt32(); |
|
|
|
|
|
|
|
Vector256<uint> eq = Avx2.CompareEqual(p, c); |
|
|
|
Vector256<uint> r = Avx2.BlendVariable(c, r256, Avx2.And(eq, vmb256)); |
|
|
|
|
|
|
|
if (nextFrame != null) |
|
|
|
{ |
|
|
|
Vector256<int> n = Avx2.ShiftRightLogical(Unsafe.Add(ref nextBase, x).AsUInt32(), 24).AsInt32(); |
|
|
|
eq = Avx2.AndNot(Avx2.CompareGreaterThan(Avx2.ShiftRightLogical(c, 24).AsInt32(), n).AsUInt32(), eq); |
|
|
|
} |
|
|
|
|
|
|
|
Unsafe.Add(ref resultBase, x) = r.AsByte(); |
|
|
|
|
|
|
|
uint msk = (uint)Avx2.MoveMask(eq.AsByte()); |
|
|
|
msk = ~msk; |
|
|
|
|
|
|
|
if (msk != 0) |
|
|
|
{ |
|
|
|
// If is diff is found, the left side is marked by the min of previously found left side and the start position.
|
|
|
|
// The right is the max of the previously found right side and the end position.
|
|
|
|
int start = i + (BitOperations.TrailingZeroCount(msk) / sizeof(uint)); |
|
|
|
int end = i + (8 - (BitOperations.LeadingZeroCount(msk) / sizeof(uint))); |
|
|
|
left = Math.Min(left, start); |
|
|
|
right = Math.Max(right, end); |
|
|
|
hasRowDiff = true; |
|
|
|
hasDiff = true; |
|
|
|
} |
|
|
|
|
|
|
|
x++; |
|
|
|
i += 8; |
|
|
|
remaining -= 8; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
for (i = remaining; i > 0; i--) |
|
|
|
{ |
|
|
|
x = (uint)(length - i); |
|
|
|
|
|
|
|
Vector4 p = Unsafe.Add(ref Unsafe.As<Vector256<float>, Vector4>(ref previousBase), x); |
|
|
|
Vector4 c = Unsafe.Add(ref Unsafe.As<Vector256<float>, Vector4>(ref currentBase), x); |
|
|
|
ref Vector4 r = ref Unsafe.Add(ref Unsafe.As<Vector256<float>, Vector4>(ref resultBase), x); |
|
|
|
Rgba32 p = Unsafe.Add(ref MemoryMarshal.GetReference(previous), x); |
|
|
|
Rgba32 c = Unsafe.Add(ref MemoryMarshal.GetReference(current), x); |
|
|
|
Rgba32 n = Unsafe.Add(ref MemoryMarshal.GetReference(next), x); |
|
|
|
ref Rgba32 r = ref Unsafe.Add(ref MemoryMarshal.GetReference(result), x); |
|
|
|
|
|
|
|
if (p != c) |
|
|
|
{ |
|
|
|
r = c; |
|
|
|
bool peq = c.Rgba == (previousFrame != null ? p.Rgba : bg.Rgba); |
|
|
|
Rgba32 val = (blend & peq) ? replacement : c; |
|
|
|
|
|
|
|
peq &= nextFrame == null || (n.Rgba >> 24 >= c.Rgba >> 24); |
|
|
|
r = val; |
|
|
|
|
|
|
|
if (!peq) |
|
|
|
{ |
|
|
|
// If is diff is found, the left side is marked by the min of previously found left side and the diff position.
|
|
|
|
// The right is the max of the previously found right side and the diff position + 1.
|
|
|
|
left = Math.Min(left, (int)x); |
|
|
|
@ -127,10 +152,6 @@ internal static class AnimationUtilities |
|
|
|
hasRowDiff = true; |
|
|
|
hasDiff = true; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
r = replacement; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (hasRowDiff) |
|
|
|
@ -143,7 +164,7 @@ internal static class AnimationUtilities |
|
|
|
bottom = y + 1; |
|
|
|
} |
|
|
|
|
|
|
|
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, result, resultFrame.DangerousGetPixelRowMemory(y).Span, PixelConversionModifiers.Scale); |
|
|
|
PixelOperations<TPixel>.Instance.FromRgba32(configuration, result, resultFrame.DangerousGetPixelRowMemory(y).Span); |
|
|
|
} |
|
|
|
|
|
|
|
Rectangle bounds = Rectangle.FromLTRB( |
|
|
|
@ -163,19 +184,6 @@ internal static class AnimationUtilities |
|
|
|
|
|
|
|
return (hasDiff, bounds); |
|
|
|
} |
|
|
|
|
|
|
|
public static void CopySource<TPixel>(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Rectangle bounds) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
Buffer2DRegion<TPixel> sourceBuffer = source.PixelBuffer.GetRegion(bounds); |
|
|
|
Buffer2DRegion<TPixel> destBuffer = destination.PixelBuffer.GetRegion(bounds); |
|
|
|
for (int y = 0; y < destBuffer.Height; y++) |
|
|
|
{ |
|
|
|
Span<TPixel> sourceRow = sourceBuffer.DangerousGetRowSpan(y); |
|
|
|
Span<TPixel> destRow = destBuffer.DangerousGetRowSpan(y); |
|
|
|
sourceRow.CopyTo(destRow); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
#pragma warning disable SA1201 // Elements should appear in the correct order
|
|
|
|
|