Skip to content

Commit 958c9c9

Browse files
Deduper works
1 parent aea44fa commit 958c9c9

10 files changed

Lines changed: 363 additions & 425 deletions

File tree

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System.Buffers;
5+
using System.Numerics;
6+
using System.Runtime.CompilerServices;
7+
using System.Runtime.InteropServices;
8+
using System.Runtime.Intrinsics;
9+
using System.Runtime.Intrinsics.X86;
10+
using SixLabors.ImageSharp.Advanced;
11+
using SixLabors.ImageSharp.Memory;
12+
using SixLabors.ImageSharp.PixelFormats;
13+
14+
namespace SixLabors.ImageSharp.Formats;
15+
16+
/// <summary>
17+
/// Utility methods for animated formats.
18+
/// </summary>
19+
internal static class AnimationUtilities
20+
{
21+
/// <summary>
22+
/// Deduplicates pixels between the previous and current frame returning only the changed pixels and bounds.
23+
/// </summary>
24+
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
25+
/// <param name="configuration">The configuration.</param>
26+
/// <param name="previousFrame">The previous frame if present.</param>
27+
/// <param name="currentFrame">The current frame.</param>
28+
/// <param name="resultFrame">The resultant output.</param>
29+
/// <param name="replacement">The value to use when replacing duplicate pixels.</param>
30+
/// <returns>The <see cref="ValueTuple{Boolean, Rectangle}"/> representing the operation result.</returns>
31+
public static (bool Difference, Rectangle Bounds) DeDuplicatePixels<TPixel>(
32+
Configuration configuration,
33+
ImageFrame<TPixel>? previousFrame,
34+
ImageFrame<TPixel> currentFrame,
35+
ImageFrame<TPixel> resultFrame,
36+
Vector4 replacement)
37+
where TPixel : unmanaged, IPixel<TPixel>
38+
{
39+
MemoryAllocator memoryAllocator = configuration.MemoryAllocator;
40+
IMemoryOwner<Vector4> buffers = memoryAllocator.Allocate<Vector4>(currentFrame.Width * 3, AllocationOptions.Clean);
41+
Span<Vector4> previous = buffers.GetSpan()[..currentFrame.Width];
42+
Span<Vector4> current = buffers.GetSpan().Slice(currentFrame.Width, currentFrame.Width);
43+
Span<Vector4> result = buffers.GetSpan()[(currentFrame.Width * 2)..];
44+
45+
int top = int.MinValue;
46+
int bottom = int.MaxValue;
47+
int left = int.MaxValue;
48+
int right = int.MinValue;
49+
50+
bool hasDiff = false;
51+
for (int y = 0; y < currentFrame.Height; y++)
52+
{
53+
if (previousFrame != null)
54+
{
55+
PixelOperations<TPixel>.Instance.ToVector4(configuration, previousFrame.DangerousGetPixelRowMemory(y).Span, previous, PixelConversionModifiers.Scale);
56+
}
57+
58+
PixelOperations<TPixel>.Instance.ToVector4(configuration, currentFrame.DangerousGetPixelRowMemory(y).Span, current, PixelConversionModifiers.Scale);
59+
60+
ref Vector256<float> previousBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(previous));
61+
ref Vector256<float> currentBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(current));
62+
ref Vector256<float> resultBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(result));
63+
64+
Vector256<float> replacement256 = Vector256.Create(replacement.X, replacement.Y, replacement.Z, replacement.W, replacement.X, replacement.Y, replacement.Z, replacement.W);
65+
66+
int size = Unsafe.SizeOf<Vector4>();
67+
68+
bool hasRowDiff = false;
69+
int i = 0;
70+
uint x = 0;
71+
int length = current.Length;
72+
int remaining = current.Length;
73+
if (Avx2.IsSupported && remaining >= 2)
74+
{
75+
while (remaining >= 2)
76+
{
77+
Vector256<float> p = Unsafe.Add(ref previousBase, x);
78+
Vector256<float> c = Unsafe.Add(ref currentBase, x);
79+
80+
// Compare the previous and current pixels
81+
Vector256<float> neq = Avx.CompareEqual(p, c);
82+
Vector256<int> mask = neq.AsInt32();
83+
84+
neq = Avx.Xor(neq, Vector256<float>.AllBitsSet);
85+
int m = Avx2.MoveMask(neq.AsByte());
86+
if (m != 0)
87+
{
88+
// If is diff is found, the left side is marked by the min of previously found left side and the diff position.
89+
// The right is the max of the previously found right side and the diff position + 1.
90+
int diff = (int)(i + (uint)(BitOperations.TrailingZeroCount(m) / size));
91+
left = Math.Min(left, diff);
92+
right = Math.Max(right, diff + 1);
93+
hasRowDiff = true;
94+
hasDiff = true;
95+
}
96+
97+
// Capture the original alpha values.
98+
mask = Avx2.HorizontalAdd(mask, mask);
99+
mask = Avx2.HorizontalAdd(mask, mask);
100+
mask = Avx2.CompareEqual(mask, Vector256.Create(-4));
101+
102+
Vector256<float> r = Avx.BlendVariable(c, replacement256, mask.AsSingle());
103+
Unsafe.Add(ref resultBase, x) = r;
104+
105+
x++;
106+
i += 2;
107+
remaining -= 2;
108+
}
109+
}
110+
111+
for (i = remaining; i > 0; i--)
112+
{
113+
x = (uint)(length - i);
114+
115+
Vector4 p = Unsafe.Add(ref Unsafe.As<Vector256<float>, Vector4>(ref previousBase), x);
116+
Vector4 c = Unsafe.Add(ref Unsafe.As<Vector256<float>, Vector4>(ref currentBase), x);
117+
ref Vector4 r = ref Unsafe.Add(ref Unsafe.As<Vector256<float>, Vector4>(ref resultBase), x);
118+
119+
if (p != c)
120+
{
121+
r = c;
122+
123+
// If is diff is found, the left side is marked by the min of previously found left side and the diff position.
124+
// The right is the max of the previously found right side and the diff position + 1.
125+
left = Math.Min(left, (int)x);
126+
right = Math.Max(right, (int)x + 1);
127+
hasRowDiff = true;
128+
hasDiff = true;
129+
}
130+
else
131+
{
132+
r = replacement;
133+
}
134+
}
135+
136+
if (hasRowDiff)
137+
{
138+
if (top == int.MinValue)
139+
{
140+
top = y;
141+
}
142+
143+
bottom = y + 1;
144+
}
145+
146+
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, result, resultFrame.DangerousGetPixelRowMemory(y).Span, PixelConversionModifiers.Scale);
147+
}
148+
149+
Rectangle bounds = Rectangle.FromLTRB(
150+
left = Numerics.Clamp(left, 0, resultFrame.Width - 1),
151+
top = Numerics.Clamp(top, 0, resultFrame.Height - 1),
152+
Numerics.Clamp(right, left + 1, resultFrame.Width),
153+
Numerics.Clamp(bottom, top + 1, resultFrame.Height));
154+
155+
return new(hasDiff, bounds);
156+
}
157+
}

src/ImageSharp/Formats/Gif/GifEncoder.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

4-
using SixLabors.ImageSharp.Advanced;
5-
64
namespace SixLabors.ImageSharp.Formats.Gif;
75

86
/// <summary>

0 commit comments

Comments
 (0)