Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions Tests/test_image_resample.py
Original file line number Diff line number Diff line change
Expand Up @@ -627,3 +627,37 @@ def test_skip_vertical(self, flt: Image.Resampling) -> None:
0.4,
f">>> {size} {box} {flt}",
)


class TestCoreResample16bpc:
def test_resampling_clamp(self) -> None:
# Lanczos weighting during downsampling can push accumulated float sums
# above 65535. These must be clamped to 65535, not corrupted byte-by-byte.
width, height = 100, 10
# I;16 image: left half = 0, right half = 65535
im_16 = Image.new("I;16", (width, height))
for y in range(height):
for x in range(width // 2, width):
im_16.putpixel((x, y), 65535)
# F image: same values as float reference
im_f = Image.new("F", (width, height))
for y in range(height):
for x in range(width // 2, width):
im_f.putpixel((x, y), 65535.0)

# 5x downsampling with Lanczos creates ~8.7% overshoot at the step edge
result_16 = im_16.resize((20, height), Image.Resampling.LANCZOS)
result_f = im_f.resize((20, height), Image.Resampling.LANCZOS)

px_16 = result_16.load()
px_f = result_f.load()
assert px_16 is not None
assert px_f is not None
for y in range(height):
for x in range(20):
v = px_f[x, y]
assert isinstance(v, float)
expected = max(0, min(65535, round(v)))
assert (
px_16[x, y] == expected
), f"Pixel ({x}, {y}): expected {expected}, got {px_16[x, y]}"
18 changes: 14 additions & 4 deletions src/libImaging/Resample.c
Original file line number Diff line number Diff line change
Expand Up @@ -493,8 +493,13 @@ ImagingResampleHorizontal_16bpc(
k[x];
}
ss_int = ROUND_UP(ss);
imOut->image8[yy][xx * 2 + (bigendian ? 1 : 0)] = CLIP8(ss_int % 256);
imOut->image8[yy][xx * 2 + (bigendian ? 0 : 1)] = CLIP8(ss_int >> 8);
if (ss_int < 0) {
ss_int = 0;
} else if (ss_int > 65535) {
ss_int = 65535;
}
imOut->image8[yy][xx * 2 + (bigendian ? 1 : 0)] = ss_int & 0xFF;
imOut->image8[yy][xx * 2 + (bigendian ? 0 : 1)] = ss_int >> 8;
}
}
ImagingSectionLeave(&cookie);
Expand Down Expand Up @@ -532,8 +537,13 @@ ImagingResampleVertical_16bpc(
k[y];
}
ss_int = ROUND_UP(ss);
imOut->image8[yy][xx * 2 + (bigendian ? 1 : 0)] = CLIP8(ss_int % 256);
imOut->image8[yy][xx * 2 + (bigendian ? 0 : 1)] = CLIP8(ss_int >> 8);
if (ss_int < 0) {
ss_int = 0;
} else if (ss_int > 65535) {
ss_int = 65535;
}
imOut->image8[yy][xx * 2 + (bigendian ? 1 : 0)] = ss_int & 0xFF;
imOut->image8[yy][xx * 2 + (bigendian ? 0 : 1)] = ss_int >> 8;
}
}
ImagingSectionLeave(&cookie);
Expand Down
Loading