{"record":{"id":"dc2c28f547a69f7b","repo":"HMCL-dev/HMCL","slug":"greyscale-supports-1-2-4-8-but-not-16","errorCode":null,"errorMessage":"Greyscale supports 1, 2, 4, 8 but not 16.","messagePattern":"Greyscale supports 1, 2, 4, 8 but not 16\\.","errorType":"exception","errorClass":"PngFeatureException","httpStatus":null,"severity":"error","filePath":"HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/Argb8888Processors.java","lineNumber":44,"sourceCode":"     * @return a concrete Argb8888Processor to transform source pixels into the specific bitmap.\n     * @throws PngException if there is a feature not supported or some specification break with the file.\n     */\n    public static Argb8888ScanlineProcessor from(PngHeader header, PngScanlineBuffer scanlineReader, Argb8888Bitmap bitmap) throws PngException {\n\n        int bytesPerScanline = header.bytesPerRow;\n        switch (header.colourType) {\n            case PNG_GREYSCALE:\n                switch (header.bitDepth) {\n                    case 1:\n                        return new IndexedColourBits(bytesPerScanline, bitmap, 7, 0x01, PngConstants.SHIFTS_1, Argb8888Palette.forGreyscale(1));\n                    case 2:\n                        return new IndexedColourBits(bytesPerScanline, bitmap, 3, 0x03, PngConstants.SHIFTS_2, Argb8888Palette.forGreyscale(2));\n                    case 4:\n                        return new IndexedColourBits(bytesPerScanline, bitmap, 1, 0x0F, PngConstants.SHIFTS_4, Argb8888Palette.forGreyscale(4));\n                    case 8:\n                        return new Greyscale8(bytesPerScanline, bitmap);\n                    case 16:\n                        throw new PngFeatureException(\"Greyscale supports 1, 2, 4, 8 but not 16.\");\n                    default:\n                        throw new PngIntegrityException(String.format(\"Invalid greyscale bit-depth: %d\", header.bitDepth)); // TODO: should be in header parse.\n                }\n\n            case PNG_GREYSCALE_WITH_ALPHA:\n                switch (header.bitDepth) {\n                    case 4:\n                        return new Greyscale4Alpha(bytesPerScanline, bitmap);\n                    case 8:\n                        return new Greyscale8Alpha(bytesPerScanline, bitmap);\n                    case 16:\n                        return new Greyscale16Alpha(bytesPerScanline, bitmap);\n                    default:\n                        throw new PngIntegrityException(String.format(\"Invalid greyscale-with-alpha bit-depth: %d\", header.bitDepth)); // TODO: should be in header parse.\n                }\n\n            case PNG_INDEXED_COLOUR:\n                switch (header.bitDepth) {","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/HMCL-dev/HMCL/blob/24702dc5a0214034f4c27166d5fd30cad08cec19/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/Argb8888Processors.java#L26-L62","documentation":"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.","triggerScenarios":"Calling Argb8888Processors.from with a header of colourType PNG_GREYSCALE and bitDepth 16.","commonSituations":"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.","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"],"exampleFix":"// before: decoding a 16-bit greyscale PNG directly\nArgb8888ScanlineProcessor p = Argb8888Processors.from(header, bitmap); // throws at bitDepth 16\n// after: downconvert first\nif (header.colourType == PngColourType.PNG_GREYSCALE && header.bitDepth == 16) {\n    header = downconvertTo8Bit(header); // or reject the file\n}\nArgb8888ScanlineProcessor p = Argb8888Processors.from(header, bitmap);","handlingStrategy":"fallback","validationCode":"if (header.colourType == PngColourType.PNG_GREYSCALE && header.bitDepth == 16) {\n    header = downconvertTo8Bit(header); // before calling from()\n}","typeGuard":"boolean isSupportedGreyscaleDepth(PngHeader header) {\n    return header.colourType != PngColourType.PNG_GREYSCALE\n        || (header.bitDepth == 1 || header.bitDepth == 2 || header.bitDepth == 4 || header.bitDepth == 8);\n}","tryCatchPattern":"try {\n    processor = Argb8888Processors.from(header, bitmap);\n} catch (PngFeatureException e) {\n    // 16-bit greyscale not supported: route to a 16-bit-capable decoder\n    processor = fallbackDecoderFactory.from(header, bitmap);\n}","preventionTips":["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"],"tags":["png","bit-depth","unsupported-feature","greyscale"],"backgroundTag":"unsupported-dtype","analyzedSha":"24702dc5a0214034f4c27166d5fd30cad08cec19","analyzedAt":"2026-09-10T12:36:46.680Z","contentChangedAt":"2026-09-10T12:36:46.680Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}