@@ -161,6 +161,47 @@ def test_basic_to_ndarray() -> None:
161161 assert array .shape == (480 , 640 , 3 )
162162
163163
164+ def _vflip (frame : VideoFrame ) -> VideoFrame :
165+ """Vertically flip a frame, which yields a bottom-up frame with a negative
166+ ``line_size`` (the same layout DirectShow produces, see GH-2213)."""
167+ graph = av .filter .Graph ()
168+ src = graph .add_buffer (
169+ template = None ,
170+ width = frame .width ,
171+ height = frame .height ,
172+ format = frame .format .name ,
173+ time_base = Fraction (1 , 1000 ),
174+ )
175+ vflip = graph .add ("vflip" )
176+ sink = graph .add ("buffersink" )
177+ src .link_to (vflip )
178+ vflip .link_to (sink )
179+ graph .configure ()
180+ graph .push (frame )
181+ return graph .pull ()
182+
183+
184+ @pytest .mark .parametrize ("format" , ["rgb24" , "bgr24" , "gray" ])
185+ def test_negative_linesize_to_ndarray (format : str ) -> None :
186+ # Bottom-up packed frames have a negative line_size; to_ndarray() must read
187+ # them without crashing (GH-2213) and in the correct top-down order.
188+ height , width = 6 , 4
189+ if format == "gray" :
190+ array = numpy .arange (height * width , dtype = numpy .uint8 ).reshape (height , width )
191+ else :
192+ array = numpy .zeros ((height , width , 3 ), dtype = numpy .uint8 )
193+ for row in range (height ):
194+ array [row , :, :] = row * 10
195+
196+ frame = _vflip (VideoFrame .from_ndarray (array , format = format ))
197+ assert frame .planes [0 ].line_size < 0
198+
199+ result = frame .to_ndarray (format = format )
200+ assertNdarraysEqual (result , array [::- 1 ])
201+ # Fully materializing the array used to segfault on a bottom-up frame.
202+ assert result .copy ().sum () == int (array .sum ())
203+
204+
164205def test_ndarray_gray () -> None :
165206 array = numpy .random .randint (0 , 256 , size = (480 , 640 ), dtype = numpy .uint8 )
166207 for format in ("gray" , "gray8" ):
0 commit comments