@@ -27,15 +27,19 @@ internal static class AnimationUtilities
2727 /// <param name="currentFrame">The current frame.</param>
2828 /// <param name="resultFrame">The resultant output.</param>
2929 /// <param name="replacement">The value to use when replacing duplicate pixels.</param>
30+ /// <param name="clampingMode">The clamping bound to apply when calculating difference bounds.</param>
3031 /// <returns>The <see cref="ValueTuple{Boolean, Rectangle}"/> representing the operation result.</returns>
3132 public static ( bool Difference , Rectangle Bounds ) DeDuplicatePixels < TPixel > (
3233 Configuration configuration ,
3334 ImageFrame < TPixel > ? previousFrame ,
3435 ImageFrame < TPixel > currentFrame ,
3536 ImageFrame < TPixel > resultFrame ,
36- Vector4 replacement )
37+ Vector4 replacement ,
38+ ClampingMode clampingMode = ClampingMode . None )
3739 where TPixel : unmanaged, IPixel < TPixel >
3840 {
41+ // TODO: This would be faster (but more complicated to find diff bounds) if we operated on Rgba32.
42+ // If someone wants to do that, they have my unlimited thanks.
3943 MemoryAllocator memoryAllocator = configuration . MemoryAllocator ;
4044 IMemoryOwner < Vector4 > buffers = memoryAllocator . Allocate < Vector4 > ( currentFrame . Width * 3 , AllocationOptions . Clean ) ;
4145 Span < Vector4 > previous = buffers . GetSpan ( ) [ ..currentFrame . Width ] ;
@@ -78,10 +82,11 @@ public static (bool Difference, Rectangle Bounds) DeDuplicatePixels<TPixel>(
7882 Vector256 < float > c = Unsafe . Add ( ref currentBase , x ) ;
7983
8084 // Compare the previous and current pixels
81- Vector256 < float > neq = Avx . CompareEqual ( p , c ) ;
82- Vector256 < int > mask = neq . AsInt32 ( ) ;
85+ Vector256 < int > mask = Avx2 . CompareEqual ( p . AsInt32 ( ) , c . AsInt32 ( ) ) ;
86+ mask = Avx2 . CompareEqual ( mask . AsInt64 ( ) , Vector256 < long > . AllBitsSet ) . AsInt32 ( ) ;
87+ mask = Avx2 . And ( mask , Avx2 . Shuffle ( mask , 0b_01_00_11_10 ) ) . AsInt32 ( ) ;
8388
84- neq = Avx . Xor ( neq , Vector256 < float > . AllBitsSet ) ;
89+ Vector256 < int > neq = Avx2 . Xor ( mask . AsInt64 ( ) , Vector256 < long > . AllBitsSet ) . AsInt32 ( ) ;
8590 int m = Avx2 . MoveMask ( neq . AsByte ( ) ) ;
8691 if ( m != 0 )
8792 {
@@ -95,11 +100,7 @@ public static (bool Difference, Rectangle Bounds) DeDuplicatePixels<TPixel>(
95100 hasDiff = true ;
96101 }
97102
98- // Capture the original alpha values.
99- mask = Avx2 . HorizontalAdd ( mask , mask ) ;
100- mask = Avx2 . HorizontalAdd ( mask , mask ) ;
101- mask = Avx2 . CompareEqual ( mask , Vector256 . Create ( - 4 ) ) ;
102-
103+ // Replace the pixel value with the replacement if the full pixel is matched.
103104 Vector256 < float > r = Avx . BlendVariable ( c , replacement256 , mask . AsSingle ( ) ) ;
104105 Unsafe . Add ( ref resultBase , x ) = r;
105106
@@ -153,6 +154,37 @@ public static (bool Difference, Rectangle Bounds) DeDuplicatePixels<TPixel>(
153154 Numerics . Clamp ( right , left + 1 , resultFrame . Width ) ,
154155 Numerics . Clamp ( bottom , top + 1 , resultFrame . Height ) ) ;
155156
157+ // Webp requires even bounds
158+ if ( clampingMode == ClampingMode . Even )
159+ {
160+ bounds . Width = Math . Min ( resultFrame . Width , bounds . Width + ( bounds . X & 1 ) ) ;
161+ bounds . Height = Math . Min ( resultFrame . Height , bounds . Height + ( bounds . Y & 1 ) ) ;
162+ bounds . X = Math . Max ( 0 , bounds . X - ( bounds . X & 1 ) ) ;
163+ bounds . Y = Math . Max ( 0 , bounds . Y - ( bounds . Y & 1 ) ) ;
164+ }
165+
156166 return new ( hasDiff , bounds ) ;
157167 }
168+
169+ public static void CopySource < TPixel > ( ImageFrame < TPixel > source , ImageFrame < TPixel > destination , Rectangle bounds )
170+ where TPixel : unmanaged, IPixel < TPixel >
171+ {
172+ Buffer2DRegion < TPixel > sourceBuffer = source . PixelBuffer . GetRegion ( bounds ) ;
173+ Buffer2DRegion < TPixel > destBuffer = destination . PixelBuffer . GetRegion ( bounds ) ;
174+ for ( int y = 0 ; y < destination . Height ; y ++ )
175+ {
176+ Span < TPixel > sourceRow = sourceBuffer . DangerousGetRowSpan ( y ) ;
177+ Span < TPixel > destRow = destBuffer . DangerousGetRowSpan ( y ) ;
178+ sourceRow . CopyTo ( destRow ) ;
179+ }
180+ }
181+ }
182+
183+ #pragma warning disable SA1201 // Elements should appear in the correct order
184+ internal enum ClampingMode
185+ #pragma warning restore SA1201 // Elements should appear in the correct order
186+ {
187+ None ,
188+
189+ Even ,
158190}
0 commit comments