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 and
+NaN in
magick/threshold.c/ClampPixel(). -NaN would be black, +NaN would be white.