OK. It turns out that the incorrect pixel wasn't black, it was transparent.
The bug occurs when an alpha-map file specifying an entirely opaque image is used. If -force is not specified, pnmtopng converts from an alpha mask to a transparency index, using a pixel generated from uninitialised data on the stack (which would be why pre-linking the libraries gave a different result - probably still wrong, but less visible). The attached patch does three things: 1. alpha_trans() no longer modifies *alpha_transcolorP unless it's valid (i.e. *alpha_can_be_transparency_indexP is being set to TRUE) 2. We no longer say that we can be made transparent if the alphamap is completely opaque. 3. We now don't produce any alpha information if the alpha map is completely opaque unless -force is specified.
diff -u3 -r netpbm-free-10.0/pnm/pnmtopng.c netpbm-free-10.0.patched/pnm/pnmtopng.c --- netpbm-free-10.0/pnm/pnmtopng.c 2005-08-26 03:25:17.000000000 +0000 +++ netpbm-free-10.0.patched/pnm/pnmtopng.c 2005-08-26 04:30:33.000000000 +0000 @@ -566,7 +566,8 @@ xelval const maxval, int const format, gray ** const alpha_mask, gray alpha_maxval, bool * const alpha_can_be_transparency_indexP, - pixel* const alpha_transcolorP) { + pixel* const alpha_transcolorP, + bool * const alpha_is_completely_opaqueP) { /*---------------------------------------------------------------------------- Check if the alpha mask can be represented by a single transparency value (i.e. all colors fully opaque except one fully transparent; @@ -583,6 +584,7 @@ /* We found a pixel in the image where the alpha mask says it is transparent. */ + bool completely_opaque; pixel transcolor; /* Color of the transparent pixel mentioned above. */ int const pnm_type = PNM_FORMAT_TYPE(format); @@ -594,7 +596,9 @@ /* Find a candidate transparent color -- the color of any pixel in the image that the alpha mask says should be transparent. */ + completely_opaque = TRUE; found_transparent_pixel = FALSE; /* initial assumption */ + retval = FALSE; /* if we don't find any transparent pixels */ pm_seek(ifp, imagepos); for (row = 0 ; row < rows && !found_transparent_pixel ; ++row) { int col; @@ -602,7 +606,10 @@ for (col = 0 ; col < cols && !found_transparent_pixel; ++col) { if (alpha_mask[row][col] == 0) { found_transparent_pixel = TRUE; + completely_opaque = FALSE; transcolor = xeltopixel(xelrow[col]); + } else if (alpha_mask[row][col] != alpha_maxval) { + completely_opaque = FALSE; } } } @@ -651,7 +658,10 @@ pnm_freerow(xelrow); *alpha_can_be_transparency_indexP = retval; - *alpha_transcolorP = transcolor; + *alpha_is_completely_opaqueP = completely_opaque; + if (retval) { + *alpha_transcolorP = transcolor; + } } @@ -1104,6 +1114,7 @@ int alpha_rows; int alpha_cols; int alpha_can_be_transparency_index; + int alpha_is_completely_opaque; int colors; int fulldepth; @@ -1201,7 +1212,8 @@ } alpha_trans(ifp, imagepos, cols, rows, maxval, format, alpha_mask, alpha_maxval, - &alpha_can_be_transparency_index, &alpha_transcolor); + &alpha_can_be_transparency_index, + &alpha_transcolor, &alpha_is_completely_opaque); if (alpha_can_be_transparency_index && !force) { if (verbose) @@ -1210,7 +1222,14 @@ transparent = 2; transcolor = alpha_transcolor; } else { - transparent = -1; + if (alpha_is_completely_opaque && !force) { + if (verbose) + pm_message ("converting opaque alpha mask to non-transparent image"); + alpha = FALSE; + transparent = -1; + } else { + transparent = -1; + } } } else /* Though there's no alpha_mask, we still need an alpha_maxval for