@@ -25,26 +25,31 @@ internal static class AnimationUtilities
2525 /// <param name="configuration">The configuration.</param>
2626 /// <param name="previousFrame">The previous frame if present.</param>
2727 /// <param name="currentFrame">The current frame.</param>
28+ /// <param name="nextFrame">The next frame if present.</param>
2829 /// <param name="resultFrame">The resultant output.</param>
2930 /// <param name="replacement">The value to use when replacing duplicate pixels.</param>
31+ /// <param name="blend">Whether the resultant frame represents an animation blend.</param>
3032 /// <param name="clampingMode">The clamping bound to apply when calculating difference bounds.</param>
3133 /// <returns>The <see cref="ValueTuple{Boolean, Rectangle}"/> representing the operation result.</returns>
3234 public static ( bool Difference , Rectangle Bounds ) DeDuplicatePixels < TPixel > (
3335 Configuration configuration ,
3436 ImageFrame < TPixel > ? previousFrame ,
3537 ImageFrame < TPixel > currentFrame ,
38+ ImageFrame < TPixel > ? nextFrame ,
3639 ImageFrame < TPixel > resultFrame ,
37- Vector4 replacement ,
40+ Color replacement ,
41+ bool blend ,
3842 ClampingMode clampingMode = ClampingMode . None )
3943 where TPixel : unmanaged, IPixel < TPixel >
4044 {
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.
4345 MemoryAllocator memoryAllocator = configuration . MemoryAllocator ;
44- IMemoryOwner < Vector4 > buffers = memoryAllocator . Allocate < Vector4 > ( currentFrame . Width * 3 , AllocationOptions . Clean ) ;
45- Span < Vector4 > previous = buffers . GetSpan ( ) [ ..currentFrame . Width ] ;
46- Span < Vector4 > current = buffers . GetSpan ( ) . Slice ( currentFrame . Width , currentFrame . Width ) ;
47- Span < Vector4 > result = buffers . GetSpan ( ) [ ( currentFrame . Width * 2 ) ..] ;
46+ IMemoryOwner < Rgba32 > buffers = memoryAllocator . Allocate < Rgba32 > ( currentFrame . Width * 4 , AllocationOptions . Clean ) ;
47+ Span < Rgba32 > previous = buffers . GetSpan ( ) [ ..currentFrame . Width ] ;
48+ Span < Rgba32 > current = buffers . GetSpan ( ) . Slice ( currentFrame . Width , currentFrame . Width ) ;
49+ Span < Rgba32 > next = buffers . GetSpan ( ) . Slice ( currentFrame . Width * 2 , currentFrame . Width ) ;
50+ Span < Rgba32 > result = buffers . GetSpan ( ) [ ( currentFrame . Width * 3 ) ..] ;
51+
52+ Rgba32 bg = replacement ;
4853
4954 int top = int . MinValue ;
5055 int bottom = int . MaxValue ;
@@ -56,81 +61,97 @@ public static (bool Difference, Rectangle Bounds) DeDuplicatePixels<TPixel>(
5661 {
5762 if ( previousFrame != null )
5863 {
59- PixelOperations < TPixel > . Instance . ToVector4 ( configuration , previousFrame . DangerousGetPixelRowMemory ( y ) . Span , previous , PixelConversionModifiers . Scale ) ;
64+ PixelOperations < TPixel > . Instance . ToRgba32 ( configuration , previousFrame . DangerousGetPixelRowMemory ( y ) . Span , previous ) ;
6065 }
6166
62- PixelOperations < TPixel > . Instance . ToVector4 ( configuration , currentFrame . DangerousGetPixelRowMemory ( y ) . Span , current , PixelConversionModifiers . Scale ) ;
63-
64- ref Vector256 < float > previousBase = ref Unsafe . As < Vector4 , Vector256 < float > > ( ref MemoryMarshal . GetReference ( previous ) ) ;
65- ref Vector256 < float > currentBase = ref Unsafe . As < Vector4 , Vector256 < float > > ( ref MemoryMarshal . GetReference ( current ) ) ;
66- ref Vector256 < float > resultBase = ref Unsafe . As < Vector4 , Vector256 < float > > ( ref MemoryMarshal . GetReference ( result ) ) ;
67+ PixelOperations < TPixel > . Instance . ToRgba32 ( configuration , currentFrame . DangerousGetPixelRowMemory ( y ) . Span , current ) ;
6768
68- Vector256 < float > replacement256 = Vector256 . Create ( replacement . X , replacement . Y , replacement . Z , replacement . W , replacement . X , replacement . Y , replacement . Z , replacement . W ) ;
69+ if ( nextFrame != null )
70+ {
71+ PixelOperations < TPixel > . Instance . ToRgba32 ( configuration , nextFrame . DangerousGetPixelRowMemory ( y ) . Span , next ) ;
72+ }
6973
70- int size = Unsafe . SizeOf < Vector4 > ( ) ;
74+ ref Vector256 < byte > previousBase = ref Unsafe . As < Rgba32 , Vector256 < byte > > ( ref MemoryMarshal . GetReference ( previous ) ) ;
75+ ref Vector256 < byte > currentBase = ref Unsafe . As < Rgba32 , Vector256 < byte > > ( ref MemoryMarshal . GetReference ( current ) ) ;
76+ ref Vector256 < byte > nextBase = ref Unsafe . As < Rgba32 , Vector256 < byte > > ( ref MemoryMarshal . GetReference ( next ) ) ;
77+ ref Vector256 < byte > resultBase = ref Unsafe . As < Rgba32 , Vector256 < byte > > ( ref MemoryMarshal . GetReference ( result ) ) ;
7178
72- bool hasRowDiff = false ;
7379 int i = 0 ;
7480 uint x = 0 ;
81+ bool hasRowDiff = false ;
7582 int length = current . Length ;
7683 int remaining = current . Length ;
7784
78- while ( Avx2 . IsSupported && remaining >= 2 )
85+ if ( Avx2 . IsSupported && remaining >= 8 )
7986 {
80- Vector256 < float > p = Unsafe . Add ( ref previousBase , x ) ;
81- Vector256 < float > c = Unsafe . Add ( ref currentBase , x ) ;
82-
83- // Compare the previous and current pixels
84- Vector256 < int > mask = Avx2 . CompareEqual ( p . AsInt32 ( ) , c . AsInt32 ( ) ) ;
85- mask = Avx2 . CompareEqual ( mask . AsInt64 ( ) , Vector256 < long > . AllBitsSet ) . AsInt32 ( ) ;
86- mask = Avx2 . And ( mask , Avx2 . Shuffle ( mask , 0b_01_00_11_10 ) ) . AsInt32 ( ) ;
87-
88- Vector256 < int > neq = Avx2 . Xor ( mask . AsInt64 ( ) , Vector256 < long > . AllBitsSet ) . AsInt32 ( ) ;
89- int m = Avx2 . MoveMask ( neq . AsByte ( ) ) ;
90- if ( m != 0 )
87+ Vector256 < uint > r256 = previousFrame != null ? Vector256 . Create ( bg . PackedValue ) : Vector256 < uint > . Zero ;
88+ Vector256 < uint > vmb256 = Vector256 < uint > . Zero ;
89+ if ( blend )
9190 {
92- // If is diff is found, the left side is marked by the min of previously found left side and the start position.
93- // The right is the max of the previously found right side and the end position.
94- int start = i + ( BitOperations . TrailingZeroCount ( m ) / size ) ;
95- int end = i + ( 2 - ( BitOperations . LeadingZeroCount ( ( uint ) m ) / size ) ) ;
96- left = Math . Min ( left , start ) ;
97- right = Math . Max ( right , end ) ;
98- hasRowDiff = true ;
99- hasDiff = true ;
91+ vmb256 = Avx2 . CompareEqual ( vmb256 , vmb256 ) ;
10092 }
10193
102- // Replace the pixel value with the replacement if the full pixel is matched.
103- Vector256 < float > r = Avx . BlendVariable ( c , replacement256 , mask . AsSingle ( ) ) ;
104- Unsafe . Add ( ref resultBase , x ) = r;
105-
106- x ++ ;
107- i += 2 ;
108- remaining -= 2 ;
94+ while ( remaining >= 8 )
95+ {
96+ Vector256 < uint > p = Unsafe . Add ( ref previousBase , x ) . AsUInt32 ( ) ;
97+ Vector256 < uint > c = Unsafe . Add ( ref currentBase , x ) . AsUInt32 ( ) ;
98+
99+ Vector256 < uint > eq = Avx2 . CompareEqual ( p , c ) ;
100+ Vector256 < uint > r = Avx2 . BlendVariable ( c , r256 , Avx2 . And ( eq , vmb256 ) ) ;
101+
102+ if ( nextFrame != null )
103+ {
104+ Vector256 < int > n = Avx2 . ShiftRightLogical ( Unsafe . Add ( ref nextBase , x ) . AsUInt32 ( ) , 24 ) . AsInt32 ( ) ;
105+ eq = Avx2 . AndNot ( Avx2 . CompareGreaterThan ( Avx2 . ShiftRightLogical ( c , 24 ) . AsInt32 ( ) , n ) . AsUInt32 ( ) , eq ) ;
106+ }
107+
108+ Unsafe . Add ( ref resultBase , x ) = r. AsByte ( ) ;
109+
110+ uint msk = ( uint ) Avx2 . MoveMask ( eq . AsByte ( ) ) ;
111+ msk = ~ msk ;
112+
113+ if ( msk != 0 )
114+ {
115+ // If is diff is found, the left side is marked by the min of previously found left side and the start position.
116+ // The right is the max of the previously found right side and the end position.
117+ int start = i + ( BitOperations . TrailingZeroCount ( msk ) / sizeof ( uint ) ) ;
118+ int end = i + ( 8 - ( BitOperations . LeadingZeroCount ( msk ) / sizeof ( uint ) ) ) ;
119+ left = Math . Min ( left , start ) ;
120+ right = Math . Max ( right , end ) ;
121+ hasRowDiff = true ;
122+ hasDiff = true ;
123+ }
124+
125+ x ++ ;
126+ i += 8 ;
127+ remaining -= 8 ;
128+ }
109129 }
110130
111131 for ( i = remaining ; i > 0 ; i -- )
112132 {
113133 x = ( uint ) ( length - i ) ;
114134
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 ) ;
135+ Rgba32 p = Unsafe . Add ( ref MemoryMarshal . GetReference ( previous ) , x ) ;
136+ Rgba32 c = Unsafe . Add ( ref MemoryMarshal . GetReference ( current ) , x ) ;
137+ Rgba32 n = Unsafe . Add ( ref MemoryMarshal . GetReference ( next ) , x ) ;
138+ ref Rgba32 r = ref Unsafe . Add ( ref MemoryMarshal . GetReference ( result ) , x ) ;
118139
119- if ( p != c )
120- {
121- r = c ;
140+ bool peq = c . Rgba == ( previousFrame != null ? p . Rgba : bg . Rgba ) ;
141+ Rgba32 val = ( blend & peq ) ? replacement : c ;
142+
143+ peq &= nextFrame == null || ( n . Rgba >> 24 >= c . Rgba >> 24 ) ;
144+ r = val ;
122145
146+ if ( ! peq )
147+ {
123148 // If is diff is found, the left side is marked by the min of previously found left side and the diff position.
124149 // The right is the max of the previously found right side and the diff position + 1.
125150 left = Math . Min ( left , ( int ) x ) ;
126151 right = Math . Max ( right , ( int ) x + 1 ) ;
127152 hasRowDiff = true ;
128153 hasDiff = true ;
129154 }
130- else
131- {
132- r = replacement ;
133- }
134155 }
135156
136157 if ( hasRowDiff )
@@ -143,7 +164,7 @@ public static (bool Difference, Rectangle Bounds) DeDuplicatePixels<TPixel>(
143164 bottom = y + 1 ;
144165 }
145166
146- PixelOperations < TPixel > . Instance . FromVector4Destructive ( configuration , result , resultFrame . DangerousGetPixelRowMemory ( y ) . Span , PixelConversionModifiers . Scale ) ;
167+ PixelOperations < TPixel > . Instance . FromRgba32 ( configuration , result , resultFrame . DangerousGetPixelRowMemory ( y ) . Span ) ;
147168 }
148169
149170 Rectangle bounds = Rectangle . FromLTRB (
@@ -163,19 +184,6 @@ public static (bool Difference, Rectangle Bounds) DeDuplicatePixels<TPixel>(
163184
164185 return ( hasDiff , bounds ) ;
165186 }
166-
167- public static void CopySource < TPixel > ( ImageFrame < TPixel > source , ImageFrame < TPixel > destination , Rectangle bounds )
168- where TPixel : unmanaged, IPixel < TPixel >
169- {
170- Buffer2DRegion < TPixel > sourceBuffer = source . PixelBuffer . GetRegion ( bounds ) ;
171- Buffer2DRegion < TPixel > destBuffer = destination . PixelBuffer . GetRegion ( bounds ) ;
172- for ( int y = 0 ; y < destBuffer . Height ; y ++ )
173- {
174- Span < TPixel > sourceRow = sourceBuffer . DangerousGetRowSpan ( y ) ;
175- Span < TPixel > destRow = destBuffer . DangerousGetRowSpan ( y ) ;
176- sourceRow . CopyTo ( destRow ) ;
177- }
178- }
179187}
180188
181189#pragma warning disable SA1201 // Elements should appear in the correct order
0 commit comments