Repository navigation
Keep negative values when filtering I mode images - #10466
Conversation
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Hi. You can see that our tests are failing with your suggested change. |
Filtering a mode "I" image now keeps the negative values that the unsigned I;16 modes clamp to zero, so test_consistency_i16 can no longer compare the two results directly. Image.convert() applies the same clamp, so convert the reference into the mode under test instead of converting the result to "I". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Thanks — that was That test compared the mode I have pushed a fix that converts the reference into the mode under test instead of converting the result to |
clip32() saturates at both ends of the range, but only the lower bound was tested. Add 2**31 - 1 to the identity-kernel loop, so that both explicit return statements are exercised. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Tests are green now. The only remaining red is |
|
The remaining failure, It is a pre-existing flake. The test asserts that the first five pixels of column 0 are not all equal, and with I would rather not touch it from this PR. Happy to open a separate one making that test deterministic if you would like it. |
|
The incorrect clipping to zero was added in #7108 You've undone some of the performance improvements from python-pillow/Pillow#9736, so I've put those back in. |
125b895 to
d37f0af
Compare
d37f0af to
b5817f7
Compare
clip32()insrc/libImaging/Filter.cclamps its lower bound to zero, but modeIis a32-bit signed integer mode, so every negative pixel that goes through the convolution
becomes
0:BLUR,SHARPEN,DETAIL,EMBOSSand anyImageFilter.Kernelare affected the sameway; all of these have unity gain, so a flat image should come back unchanged (it does for
1000). The border pixels are copied withmemcpy(), so only the interior is corrupted,which leaves the filtered image self-inconsistent:
The
// Clamp the low bound branchlessly (maxss)comment is correct forclip8()andclip16(), whose modes are unsigned, butclip32()only hasImagingFilter3x3()andImagingFilter5x5()as callers, both forIMAGING_TYPE_INT32.Two things were needed. The lower bound now clamps to
INT32_MINinstead of0, and thecast rounds towards negative infinity:
offsetalready carries a+0.5bias, and a plaincast truncates towards zero, which would still turn
-1into0. Positive values take thesame path as before, since
(INT32)inis never greater thaninfor them, so this isbit-for-bit unchanged above zero. I checked that over
0 ... 3e6with a standalone programcomparing the old and new function: no differences.
I could not build Pillow locally, so CI is the check for the compiled result. The bug itself
was reproduced against current
main(the13.1.0.dev0nightly, which already containsc4888f336), and the arithmetic of the replacement was verified separately in C for therange bounds, the
2**24neighbourhood and negative values.One related issue is left alone here: the accumulator is a
float, so modeIvalues above2**24lose precision (1000000004comes back as1000000000). That is a separate changeand I did not want to mix a precision and performance decision into this fix.
🤖 Generated with Claude Code