No, identify reports TrueColor, and it's straight from the scanner so it's unlikely. I did
find the source of the bug though, and it's internal to ImageMagick and relates to how Not-a-Number floats are handled.
I performed some additional tests. I got the same thing to break using rose:
convert-hdr rose: -level 20,100%,1.5 -clamp -depth 8 x:
After having performed a lot of tests with -level, -clamp, and HDRI vs. non-HDRI ImageMagick I think I have found that it is the -level
operator that breaks here, and more explicitly, it breaks iff
these conditions are true:
- convert is compiled with HDRI support.
- The -level operator drives a dark pixel negative. (convert rose: -level 20%,100%,1.5...)
- The -level operator is applying a gamma != 1.0. (convert rose: -level 20%,100%,1.5...)
- The image depth is then set to 8 bpc. (convert rose: -level 20%,100%,1.5 -clamp -depth 8...)
So I got a bit curious, and dug through the source code to find the reason for this. The culprit is the gamma operation in magick/enhance.c/LevelPixel()
, which does a call to pow()
with a negative value due to the stretch, thus returning -NaN
(Not a Number). Nothing similar happens with bright pixels, they just return a very large but valid number.
This -NaN is stored in the image and propagated through most other functions, since almost every operation on a NaN results in a NaN. Clamping the image with -clamp
doesn't help, because the NaN is propagated.
Then we reach magick/attribute.c/SetImageChannelDepth()
which for the HDRI version calls ScaleAnyToQuantum(ScaleQuantumToAny())
for every pixel in every channel. ScaleQuantumToAny() actually converts the HDRI floats to an integer
This is where the silly things happen. On my platform, gcc 4.7.2 on a Core2 Quad, running 64-bit Ubuntu, casting a NaN to an integer that is 4 bytes or less returns 0. Casting a NaN to an integer larger than 4 bytes returns 9223372036854775808. This is exactly why the program breaks when I use -depth 8
but doesn't break as much when I use PNG24
. PNG24 doesn't use ScaleQuantumToAny, but rather ScaleQuantumToChar
. This turns the NaN pixels black instead of white, and thus it makes the error invisible. ScaleQuantumToAny
casts it to a large integer, likely 8 bytes, and thus the pixel gets a really huge value instead of 0, so it turns white.
I'm not sure what the best solution is. This problem is likely to happen everywhere
gamma correction is taking place on pixels with negative values, so fixing it may be difficult. One option is to check for -Nan
. -NaN would be black, +NaN would be white.