diff --git a/code/anim/packunpack.cpp b/code/anim/packunpack.cpp index 0af7526ffe..9b4f44a67c 100644 --- a/code/anim/packunpack.cpp +++ b/code/anim/packunpack.cpp @@ -142,11 +142,15 @@ int unpack_pixel(anim_instance *ai, ubyte *data, ubyte pix, int aabitmap, int bp bit_16 = (ushort)pix; break; case 8: - // 8 bit-per-pixel aa bitmaps are a bit special since they only use a palette index value in the range [0, 15]. These - // palette indexes must be remapped to alpha values between [0, 255] which is what graphics code expects. Palette - // range [0, 14] is a gradient from black to white, and palette index 15 is a special color which indicates the background - // area of a HUD gauge. Retail code uses the final alpha value for index 1 for this special index to give gauges a dark - // transparent background. + // 8-bit-per-pixel HUD gauge ANI aabitmaps use the palette index directly as an alpha value — + // the actual RGB colors stored in the palette are completely ignored. The valid range is [0, 15]: + // [0]: fully transparent (alpha 0) + // [1-14]: black-to-white gradient (alpha = index * 18) + // [15]: special HUD background (alpha 18, matching index 1, for a dark transparent tint) + // [>15]: clamped to fully opaque (alpha 255) + // Because only the index value matters, the palette in the source file must be ordered so that + // index 0 is the transparent end and index 14 is the opaque end. A reversed palette causes the + // gauge to render as a solid white square (see GitHub issue #4148). if (pix > 15) { bit_8 = 255; } @@ -240,11 +244,15 @@ int unpack_pixel_count(anim_instance *ai, ubyte *data, ubyte pix, int count = 0, bit_16 = (ushort)pix; break; case 8 : - // 8 bit-per-pixel aa bitmaps are a bit special since they only use a palette index value in the range [0, 15]. These - // palette indexes must be remapped to alpha values between [0, 255] which is what graphics code expects. Palette - // range [0, 14] is a gradient from black to white, and palette index 15 is a special color which indicates the background - // area of a HUD gauge. Retail code uses the final alpha value for index 1 for this special index to give gauges a dark - // transparent background. + // 8-bit-per-pixel HUD gauge ANI aabitmaps use the palette index directly as an alpha value — + // the actual RGB colors stored in the palette are completely ignored. The valid range is [0, 15]: + // [0]: fully transparent (alpha 0) + // [1-14]: black-to-white gradient (alpha = index * 18) + // [15]: special HUD background (alpha 18, matching index 1, for a dark transparent tint) + // [>15]: clamped to fully opaque (alpha 255) + // Because only the index value matters, the palette in the source file must be ordered so that + // index 0 is the transparent end and index 14 is the opaque end. A reversed palette causes the + // gauge to render as a solid white square (see GitHub issue #4148). if (pix > 15) { bit_8 = 255; } diff --git a/code/pcxutils/pcxutils.cpp b/code/pcxutils/pcxutils.cpp index 27e90109c5..2dd6888375 100644 --- a/code/pcxutils/pcxutils.cpp +++ b/code/pcxutils/pcxutils.cpp @@ -312,11 +312,16 @@ int pcx_read_bitmap( const char * real_filename, ubyte *org_data, ubyte * /*pal* if ( byte_size == 1 ) { auto pixel_val = data; if (!mask_bitmap) { - // 8 bit-per-pixel aa bitmaps are a bit special since they only use values in the range [0, 15] where 15 wraps - // around back to 0. Since the rest of the code expects the value to be in the range [0, 255] the pixel value - // needs to be adjusted here. By multiplying the value with 17 the original range [0, 15] is mapped to [0, 255] - // This only applies to bitmaps that are not used as masks since mask bitmaps use higher values - // to indicate their mask area + // 8-bit-per-pixel HUD gauge PCX aabitmaps use the pixel value directly as an alpha level — + // the actual RGB colors in the palette are completely ignored. The valid range is [0, 15]: + // [0]: fully transparent (alpha 0) + // [1-14]: black-to-white gradient (alpha = value * 17) + // [15]: special HUD background (alpha 17, matching index 1, for a dark transparent tint) + // [>15]: clamped to fully transparent (alpha 0) + // Because only the pixel value matters, the palette in the source file must be ordered so that + // index 0 is the transparent end and index 14 is the opaque end. A reversed palette causes the + // gauge to render as a solid white square (see GitHub issue #4148). + // Mask bitmaps skip this block because they use the full value range for the mask area. if (data > 15) { pixel_val = 0; } else if (data == 15) {