@@ -630,7 +630,7 @@ def test_skip_vertical(self, flt: Image.Resampling) -> None:
630630
631631
632632class TestCoreResample16bpc :
633- def test_resampling_clamp (self ) -> None :
633+ def test_resampling_clamp_overflow (self ) -> None :
634634 # Lanczos weighting during downsampling can push accumulated float sums
635635 # above 65535. These must be clamped to 65535, not corrupted byte-by-byte.
636636 ims = {}
@@ -655,3 +655,29 @@ def test_resampling_clamp(self) -> None:
655655 assert (
656656 value == expected
657657 ), f"Pixel ({ x } , { y } ): expected { expected } , got { value } "
658+
659+ def test_resampling_clamp_underflow (self ) -> None :
660+ # Lanczos weighting during downsampling can push accumulated float sums
661+ # below 0. These must be clamped to 0, not corrupted byte-by-byte.
662+ ims = {}
663+ width , height = 100 , 10
664+ for mode in ("I;16" , "F" ):
665+ # Left half = 65535, right half = 0
666+ im = Image .new (mode , (width , height ))
667+ for y in range (height ):
668+ for x in range (width // 2 ):
669+ im .putpixel ((x , y ), 65535 )
670+
671+ # 5x downsampling with Lanczos creates ~8.7% undershoot at the step edge
672+ ims [mode ] = im .resize ((20 , height ), Image .Resampling .LANCZOS )
673+
674+ for y in range (height ):
675+ for x in range (20 ):
676+ v = ims ["F" ].getpixel ((x , y ))
677+ assert isinstance (v , float )
678+ expected = max (0 , min (65535 , round (v )))
679+
680+ value = ims ["I;16" ].getpixel ((x , y ))
681+ assert (
682+ value == expected
683+ ), f"Pixel ({ x } , { y } ): expected { expected } , got { value } "
0 commit comments