On Mon, 12 Oct 2026 22:50:45 GMT, Sergey Bylokhov <[email protected]> wrote:

>> I don't fully understand the suggestion.
>> But I've created a test for this that validates that the data is corrected 
>> during write.
>
> JPEGQTable.getScaledInstance(scale, false) mentioned above is specified to 
> return a table "clamped to the range 1..32767" 
> ([JPEGQTable.java#L179](https://github.com/openjdk/jdk/blob/f2fd22f7a23505bef47227a97a51bc9e1837111e/src/java.desktop/share/classes/javax/imageio/plugins/jpeg/JPEGQTable.java#L179)),
>  and such a table can be passed to the writer (metadata `dqtable` node, 
> `JPEGImageWriteParam.setEncodeTables`) and to the reader 
> (`JPEGImageReadParam.setDecodeTables`). Before this patch the libjpeg seems 
> writes a 16bit data, and the result decodes. After the patch every entry 
> above 255 is silently clamped to 255, on the write and decode since the code 
> path is shared.
> 
> So I do not think this is accurate:
>>ImageIO's native JPEG library is compiled to support baseline JPEG only. This 
>>means 8 bit quantization values in the range 1 -> 255.

Something like this:

public final class JpegQTableTest {

    public static void main(String[] args) throws Exception {
        byte[] big = write(JPEGQTable.K1Luminance.getScaledInstance(20f, 
false));
        byte[] small = write(JPEGQTable.K1Luminance.getScaledInstance(20f, 
true));
        int s1 = ImageIO.read(new 
ByteArrayInputStream(big)).getRaster().getSample(0, 0, 0);
        int s2 = ImageIO.read(new 
ByteArrayInputStream(small)).getRaster().getSample(0, 0, 0);
        System.out.println("bytes: " + big.length + " vs " + small.length);
        System.out.println("sample: " + s1 + " vs " + s2);
        if (Arrays.equals(big, small) || s1 == s2) {
            throw new RuntimeException("Clamped!");
        }
    }

    private static byte[] write(JPEGQTable qTable) throws Exception {
        BufferedImage img = new BufferedImage(8, 8, 
BufferedImage.TYPE_BYTE_GRAY);
        ImageWriter w = ImageIO.getImageWritersByFormatName("JPEG").next();
        ImageWriteParam p = w.getDefaultWriteParam();
        ImageTypeSpecifier type = 
ImageTypeSpecifier.createFromRenderedImage(img);
        IIOMetadata meta = w.getDefaultImageMetadata(type, p);
        String fmt = meta.getNativeMetadataFormatName();
        Node root = meta.getAsTree(fmt);

        Node markerSeq = null;
        NodeList kids = root.getChildNodes();
        for (int i = 0; i < kids.getLength(); i++) {
            if (kids.item(i).getNodeName().equals("markerSequence")) {
                markerSeq = kids.item(i);
            }
        }
        if (markerSeq == null) throw new RuntimeException("no markerSequence");

        // Replace existing dqt tables
        for (int i = 0; i < markerSeq.getChildNodes().getLength(); i++) {
            Node c = markerSeq.getChildNodes().item(i);
            if (c.getNodeName().equals("dqt")) {
                for (int j = 0; j < c.getChildNodes().getLength(); j++) {
                    IIOMetadataNode qt = 
(IIOMetadataNode)(c.getChildNodes().item(j));
                    qt.setUserObject(qTable);
                }
            }
        }
        meta.setFromTree(fmt, root);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ImageOutputStream ios = ImageIO.createImageOutputStream(baos);
        w.setOutput(ios);
        w.write(null, new IIOImage(img, null, meta), p);
        ios.flush();
        return baos.toByteArray();
    }
}

-------------

PR Review Comment: https://git.openjdk.org/jdk/pull/33217#discussion_r4253034057

Reply via email to