@@ -93,17 +93,21 @@ fn frame_source_decode_3_frames() {
9393 assert_eq ! ( source. width( ) , 8 ) ;
9494 assert_eq ! ( source. height( ) , 8 ) ;
9595
96- drain ( & mut source) ;
96+ let frame_count = 3usize ;
97+ let frame0_pixels = drain ( & mut source) ;
98+ assert_pixel_gray ( & frame0_pixels, 0 , frame_count) ;
9799
98100 assert ! ( source. advance_frame( ) . unwrap( ) ) ;
99101 let info = source. frame_info ( ) . unwrap ( ) ;
100102 assert_eq ! ( info. index, 1 ) ;
101- drain ( & mut source) ;
103+ let frame1_pixels = drain ( & mut source) ;
104+ assert_pixel_gray ( & frame1_pixels, 1 , frame_count) ;
102105
103106 assert ! ( source. advance_frame( ) . unwrap( ) ) ;
104107 let info = source. frame_info ( ) . unwrap ( ) ;
105108 assert_eq ! ( info. index, 2 ) ;
106- drain ( & mut source) ;
109+ let frame2_pixels = drain ( & mut source) ;
110+ assert_pixel_gray ( & frame2_pixels, 2 , frame_count) ;
107111
108112 assert ! ( !source. advance_frame( ) . unwrap( ) ) ;
109113 assert ! ( source. frame_info( ) . is_none( ) ) ;
@@ -164,8 +168,27 @@ fn frame_sink_encode_2_frames() {
164168 . animation_frame_decoder ( Cow :: Owned ( encoded) , & [ PixelDescriptor :: RGBA8_SRGB ] )
165169 . unwrap ( ) ;
166170
167- assert ! ( verify. render_next_frame_owned( None ) . unwrap( ) . is_some( ) ) ;
168- assert ! ( verify. render_next_frame_owned( None ) . unwrap( ) . is_some( ) ) ;
171+ // Frame 0: should be reddish
172+ let frame0 = verify. render_next_frame_owned ( None ) . unwrap ( ) . expect ( "missing frame 0" ) ;
173+ let px0 = frame0. pixels ( ) . as_strided_bytes ( ) ;
174+ // Sample first pixel (RGBA)
175+ assert ! ( px0. len( ) >= 4 , "frame 0 pixel data too short" ) ;
176+ let ( r0, g0, b0) = ( px0[ 0 ] , px0[ 1 ] , px0[ 2 ] ) ;
177+ assert ! (
178+ r0 > 200 && g0 < 50 && b0 < 50 ,
179+ "frame 0 should be reddish, got r={r0} g={g0} b={b0}"
180+ ) ;
181+
182+ // Frame 1: should be bluish
183+ let frame1 = verify. render_next_frame_owned ( None ) . unwrap ( ) . expect ( "missing frame 1" ) ;
184+ let px1 = frame1. pixels ( ) . as_strided_bytes ( ) ;
185+ assert ! ( px1. len( ) >= 4 , "frame 1 pixel data too short" ) ;
186+ let ( r1, g1, b1) = ( px1[ 0 ] , px1[ 1 ] , px1[ 2 ] ) ;
187+ assert ! (
188+ b1 > 200 && r1 < 50 && g1 < 50 ,
189+ "frame 1 should be bluish, got r={r1} g={g1} b={b1}"
190+ ) ;
191+
169192 assert ! ( verify. render_next_frame_owned( None ) . unwrap( ) . is_none( ) ) ;
170193}
171194
@@ -196,11 +219,26 @@ fn transcode_gif_passthrough() {
196219 . animation_frame_decoder ( Cow :: Owned ( encoded) , & [ PixelDescriptor :: RGBA8_SRGB ] )
197220 . unwrap ( ) ;
198221
199- for i in 0 ..3 {
222+ let frame_count = 3usize ;
223+ for i in 0 ..frame_count {
224+ let frame = verify
225+ . render_next_frame_owned ( None )
226+ . unwrap ( )
227+ . unwrap_or_else ( || panic ! ( "missing frame {i}" ) ) ;
228+ let px = frame. pixels ( ) . as_strided_bytes ( ) ;
229+ assert ! ( px. len( ) >= 4 , "frame {i} pixel data too short" ) ;
230+
231+ // build_test_gif produces gray = (i+1)*255/frame_count for each frame
232+ let expected_gray = ( ( i + 1 ) * 255 / frame_count) as u8 ;
233+ let ( r, g, b, a) = ( px[ 0 ] , px[ 1 ] , px[ 2 ] , px[ 3 ] ) ;
234+ let tolerance = 32i16 ; // GIF quantization loses precision
200235 assert ! (
201- verify. render_next_frame_owned( None ) . unwrap( ) . is_some( ) ,
202- "missing frame {i}"
236+ ( r as i16 - expected_gray as i16 ) . abs( ) <= tolerance
237+ && ( g as i16 - expected_gray as i16 ) . abs( ) <= tolerance
238+ && ( b as i16 - expected_gray as i16 ) . abs( ) <= tolerance,
239+ "frame {i}: expected ~gray({expected_gray}), got r={r} g={g} b={b}"
203240 ) ;
241+ assert ! ( a > 200 , "frame {i}: alpha should be opaque, got a={a}" ) ;
204242 }
205243 assert ! ( verify. render_next_frame_owned( None ) . unwrap( ) . is_none( ) ) ;
206244}
@@ -245,15 +283,59 @@ fn transcode_gif_with_crop() {
245283
246284 assert_eq ! ( verify. info( ) . width, 4 ) ;
247285 assert_eq ! ( verify. info( ) . height, 4 ) ;
248- assert ! ( verify. render_next_frame_owned( None ) . unwrap( ) . is_some( ) ) ;
249- assert ! ( verify. render_next_frame_owned( None ) . unwrap( ) . is_some( ) ) ;
286+
287+ // Solid-color frames survive cropping — verify pixel values still match
288+ let crop_frame_count = 2usize ;
289+ for i in 0 ..crop_frame_count {
290+ let frame = verify
291+ . render_next_frame_owned ( None )
292+ . unwrap ( )
293+ . unwrap_or_else ( || panic ! ( "missing cropped frame {i}" ) ) ;
294+ let px = frame. pixels ( ) . as_strided_bytes ( ) ;
295+ assert ! ( px. len( ) >= 4 , "cropped frame {i} pixel data too short" ) ;
296+
297+ let expected_gray = ( ( i + 1 ) * 255 / crop_frame_count) as u8 ;
298+ let ( r, g, b, a) = ( px[ 0 ] , px[ 1 ] , px[ 2 ] , px[ 3 ] ) ;
299+ let tolerance = 32i16 ;
300+ assert ! (
301+ ( r as i16 - expected_gray as i16 ) . abs( ) <= tolerance
302+ && ( g as i16 - expected_gray as i16 ) . abs( ) <= tolerance
303+ && ( b as i16 - expected_gray as i16 ) . abs( ) <= tolerance,
304+ "cropped frame {i}: expected ~gray({expected_gray}), got r={r} g={g} b={b}"
305+ ) ;
306+ assert ! ( a > 200 , "cropped frame {i}: alpha should be opaque, got a={a}" ) ;
307+ }
250308 assert ! ( verify. render_next_frame_owned( None ) . unwrap( ) . is_none( ) ) ;
251309}
252310
253311// =========================================================================
254312// Helpers
255313// =========================================================================
256314
315+ /// Assert that the first pixel of RGBA frame data matches the expected gray value
316+ /// from `build_test_gif`: gray = (frame_index+1)*255/frame_count.
317+ /// Allows ±32 tolerance for GIF palette quantization.
318+ fn assert_pixel_gray ( pixel_data : & [ u8 ] , frame_index : usize , frame_count : usize ) {
319+ assert ! (
320+ pixel_data. len( ) >= 4 ,
321+ "frame {frame_index} pixel data too short ({} bytes)" ,
322+ pixel_data. len( )
323+ ) ;
324+ let expected_gray = ( ( frame_index + 1 ) * 255 / frame_count) as u8 ;
325+ let ( r, g, b, a) = ( pixel_data[ 0 ] , pixel_data[ 1 ] , pixel_data[ 2 ] , pixel_data[ 3 ] ) ;
326+ let tolerance = 32i16 ;
327+ assert ! (
328+ ( r as i16 - expected_gray as i16 ) . abs( ) <= tolerance
329+ && ( g as i16 - expected_gray as i16 ) . abs( ) <= tolerance
330+ && ( b as i16 - expected_gray as i16 ) . abs( ) <= tolerance,
331+ "frame {frame_index}: expected ~gray({expected_gray}), got r={r} g={g} b={b}"
332+ ) ;
333+ assert ! (
334+ a > 200 ,
335+ "frame {frame_index}: alpha should be opaque, got a={a}"
336+ ) ;
337+ }
338+
257339fn make_solid_source ( width : u32 , height : u32 , pixel : [ u8 ; 4 ] ) -> Box < dyn Source > {
258340 let row_bytes = width as usize * 4 ;
259341 let mut rows_produced = 0u32 ;
0 commit comments