HMCL-dev/HMCL · error · PngFeatureException
Greyscale supports 1, 2, 4, 8 but not 16.
Error message
Greyscale supports 1, 2, 4, 8 but not 16.
What it means
PngFeatureException thrown when a greyscale (colour type 0) PNG uses the legal 16-bit depth, which this ARGB8888 decoder variant deliberately does not implement. The PNG spec allows 16-bit greyscale, but this processor factory only supports 1, 2, 4, and 8 bits for that colour type. This is an unsupported-feature rejection, not a corrupt file.
Solutions
- Convert the image to 8-bit greyscale before decoding (e.g. ImageMagick: convert in.png -depth 8 out.png)
- Pre-check IHDR bit depth in your pipeline and reject/downconvert 16-bit greyscale sources
- Use a different decoder backend that supports 16-bit greyscale
- Catch PngFeatureException and fall back to an alternative processing path
Example fix
// before: decoding a 16-bit greyscale PNG directly
Argb8888ScanlineProcessor p = Argb8888Processors.from(header, bitmap); // throws at bitDepth 16
// after: downconvert first
if (header.colourType == PngColourType.PNG_GREYSCALE && header.bitDepth == 16) {
header = downconvertTo8Bit(header); // or reject the file
}
Argb8888ScanlineProcessor p = Argb8888Processors.from(header, bitmap); Defensive patterns
Strategy: fallback
Validate before calling
if (header.colourType == PngColourType.PNG_GREYSCALE && header.bitDepth == 16) {
header = downconvertTo8Bit(header); // before calling from()
} Type guard
boolean isSupportedGreyscaleDepth(PngHeader header) {
return header.colourType != PngColourType.PNG_GREYSCALE
|| (header.bitDepth == 1 || header.bitDepth == 2 || header.bitDepth == 4 || header.bitDepth == 8);
} Try / catch
try {
processor = Argb8888Processors.from(header, bitmap);
} catch (PngFeatureException e) {
// 16-bit greyscale not supported: route to a 16-bit-capable decoder
processor = fallbackDecoderFactory.from(header, bitmap);
} Prevention
- Inspect IHDR bit depth when ingesting user-supplied images
- Normalize scanner/camera output to 8-bit before feeding the decoder
- Keep a fallback decoder for 16-bit sources
- Document supported depth/colour-type combinations for your pipeline
When it happens
Trigger: Calling Argb8888Processors.from with a header of colourType PNG_GREYSCALE and bitDepth 16.
Common situations: Decoding high-quality scans or HDR-ish exports saved as 16-bit greyscale PNGs; camera/scanner pipelines that default to 16-bit output; downsampling pipelines expecting 8-bit inputs receiving 16-bit sources.
Related errors
- Valid greyscale bit depths are 1, 2, 4, 8, not
- Invalid greyscale bit-depth
- Invalid greyscale-with-alpha bit-depth
- Invalid indexed colour bit-depth
- Invalid truecolour bit-depth
AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10).
Data as JSON: /api/errors/dc2c28f547a69f7b.
Report an issue: GitHub.
Appendix: source
Thrown at HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/Argb8888Processors.java:44
* @return a concrete Argb8888Processor to transform source pixels into the specific bitmap.
* @throws PngException if there is a feature not supported or some specification break with the file.
*/
public static Argb8888ScanlineProcessor from(PngHeader header, PngScanlineBuffer scanlineReader, Argb8888Bitmap bitmap) throws PngException {
int bytesPerScanline = header.bytesPerRow;
switch (header.colourType) {
case PNG_GREYSCALE:
switch (header.bitDepth) {
case 1:
return new IndexedColourBits(bytesPerScanline, bitmap, 7, 0x01, PngConstants.SHIFTS_1, Argb8888Palette.forGreyscale(1));
case 2:
return new IndexedColourBits(bytesPerScanline, bitmap, 3, 0x03, PngConstants.SHIFTS_2, Argb8888Palette.forGreyscale(2));
case 4:
return new IndexedColourBits(bytesPerScanline, bitmap, 1, 0x0F, PngConstants.SHIFTS_4, Argb8888Palette.forGreyscale(4));
case 8:
return new Greyscale8(bytesPerScanline, bitmap);
case 16:
throw new PngFeatureException("Greyscale supports 1, 2, 4, 8 but not 16.");
default:
throw new PngIntegrityException(String.format("Invalid greyscale bit-depth: %d", header.bitDepth)); // TODO: should be in header parse.
}
case PNG_GREYSCALE_WITH_ALPHA:
switch (header.bitDepth) {
case 4:
return new Greyscale4Alpha(bytesPerScanline, bitmap);
case 8:
return new Greyscale8Alpha(bytesPerScanline, bitmap);
case 16:
return new Greyscale16Alpha(bytesPerScanline, bitmap);
default:
throw new PngIntegrityException(String.format("Invalid greyscale-with-alpha bit-depth: %d", header.bitDepth)); // TODO: should be in header parse.
}
case PNG_INDEXED_COLOUR:
switch (header.bitDepth) {View on GitHub (pinned to 24702dc5a0)