Java 中 JPEG 压缩的自定义量化表

rho*_*ncu 5 java jpeg quantization

正如标题所示,我正在尝试使用自定义量化表来压缩 JPEG 格式的图像。我的问题是无法打开生成的文件,错误是:

Quantization table 0x00 was not defined
Run Code Online (Sandbox Code Playgroud)

这就是我的代码的样子:

        JPEGImageWriteParam params = new JPEGImageWriteParam(null);
        if (mQMatrix != null) {
            JPEGHuffmanTable[] huffmanDcTables = {JPEGHuffmanTable.StdDCLuminance, JPEGHuffmanTable.StdDCChrominance};
            JPEGHuffmanTable[] huffmanAcTables = {JPEGHuffmanTable.StdACLuminance, JPEGHuffmanTable.StdACChrominance};
            dumpMatrices(mQMatrix);
            params.setEncodeTables(mQMatrix, huffmanDcTables, huffmanAcTables);
        }

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        Iterator writers = ImageIO.getImageWritersByFormatName("JPEG");
        ImageWriter imageWriter = (ImageWriter) writers.next();

        ImageOutputStream imageOutputStream = ImageIO.createImageOutputStream(outputStream);
        imageWriter.setOutput(imageOutputStream);
        imageWriter.write(null, new IIOImage(mSourceImage, null, null), params);

        mCompressedImageSize = outputStream.size();

        try (FileOutputStream fileOutputStream = new FileOutputStream(mOutFileName)) {
            fileOutputStream.write(outputStream.toByteArray());

        }
        mCompressedImage = ImageIO.read(new ByteArrayInputStream(outputStream.toByteArray()));
Run Code Online (Sandbox Code Playgroud)

我的猜测是它与元数据有关,但我没有找到解决方案。

谢谢,R。

更新:使用十六进制查看器,我确定量化表(DQT - 0xFF、0xDB 部分)不会写入输出文件。我假设我必须强迫它以某种方式写入。

更新2:所以在实际调试执行之后,我发现如果在参数对象中设置表,则不会为量化和霍夫曼表生成元数据。如果元数据丢失,则表不会写入文件中。问题是我看不出有什么办法可以自定义元数据的内容。

har*_*ldK 2

非常有趣的问题,不幸的是,这并不简单......这是我发现的:

\n\n

首先,使用JPEGImageWriteParam.setEncodeTables(...)不行。来自JavaDoc

\n\n
\n

设置用于编码缩略流的量化和霍夫曼表。

\n
\n\n

进一步来自JPEG 元数据格式规范和使用说明

\n\n
\n

这种排序实现了这样的设计意图:表应该JPEGImageWriteParams仅作为在没有其他源可用时指定表的一种方式而被包含在内,并且仅当使用已知的非标准表进行压缩写入没有表的缩写流时才会发生这种情况。

\n
\n\n

即,param 选项只能用于写入“缩写流”(没有表格的自定义 JPEG,假设回读时将提供表格)。结论:我们指定要使用 JPEG 编码的表的唯一方法是将其传递到元数据中。

\n\n

从上面提到的同一文档中,除非压缩模式为 ,否则元数据中的表将被忽略并替换MODE_COPY_FROM_METADATA,因此我们需要指定这一点。

\n\n

有关元数据结构的文档,请参阅图像元数据 DTD 。重要的部分是dqtdht带有子节点的节点,以及它们的“User object”(不要与普通 DOM“userData”混淆)。我们需要使用我们想要使用的新表来更新这些节点。

\n\n

这是我想出的代码:

\n\n
// Obtain qtables\nmQMatrix = ...;\n\n// Read source image\nImageInputStream imageInputStream = ImageIO.createImageInputStream(...);\nImageReader reader = ImageIO.getImageReaders(imageInputStream).next();\nreader.setInput(imageInputStream);\n\nmSourceImage = reader.read(0);\nIIOMetadata metadata = null;\n\n// We need the imageWriter to create the default JPEG metadata\nImageWriter imageWriter = ImageIO.getImageWritersByFormatName("JPEG").next();\n\nif (mQMatrix != null) {\n    dumpMatrices(mQMatrix);\n\n    // Obtain default image metadata data, in native JPEG format\n    metadata = imageWriter.getDefaultImageMetadata(ImageTypeSpecifier.createFromRenderedImage(m\xe2\x80\x8c\xe2\x80\x8bSourceImage), null);\n    IIOMetadataNode nativeMeta = (IIOMetadataNode) metadata.getAsTree("javax_imageio_jpeg_image_1.0");\n\n    // Update dqt to values from mQMatrix\n    NodeList dqtables = nativeMeta.getElementsByTagName("dqtable");\n    for (int i = 0; i < dqtables.getLength(); i++) {\n        IIOMetadataNode dqt = (IIOMetadataNode) dqtables.item(i);\n        int dqtId = Integer.parseInt(dqt.getAttribute("qtableId"));\n        dqt.setUserObject(mQMatrix[dqtId]);\n    }\n\n    // For some reason, we need dht explicitly defined, when using MODE_COPY_FROM_METADATA...\n    NodeList dhtables = nativeMeta.getElementsByTagName("dhtable");\n\n    // Just use defaults for dht\n    JPEGHuffmanTable[] huffmanDcTables = {JPEGHuffmanTable.StdDCLuminance, JPEGHuffmanTable.StdDCChrominance};\n    JPEGHuffmanTable[] huffmanAcTables = {JPEGHuffmanTable.StdACLuminance, JPEGHuffmanTable.StdACChrominance};\n\n    // Update dht\n    for (int i = 0; i < dhtables.getLength(); i++) {\n        IIOMetadataNode dht = (IIOMetadataNode) dhtables.item(i);\n        int dhtClass = Integer.parseInt(dht.getAttribute("class")); // 0: DC, 1: AC\n        int dhtId = Integer.parseInt(dht.getAttribute("htableId"));\n\n        dht.setUserObject(dhtClass == 0 ? huffmanDcTables[dhtId] : huffmanAcTables[dhtId]);\n    }\n\n    // Merge updated tree back (important!)\n    metadata.mergeTree("javax_imageio_jpeg_image_1.0", nativeMeta);\n}\n\nByteArrayOutputStream outputStream = new ByteArrayOutputStream();\nImageOutputStream imageOutputStream = ImageIO.createImageOutputStream(outputStream);\nimageWriter.setOutput(imageOutputStream);\n\n// See http://docs.oracle.com/javase/6/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html#tables\nJPEGImageWriteParam params = new JPEGImageWriteParam(null);\nparams.setCompressionMode(metadata == null ? MODE_DEFAULT : MODE_COPY_FROM_METADATA); // Unless MODE_COPY_FROM_METADATA, tables will be created!\n\nimageWriter.write(null, new IIOImage(mSourceImage, null, metadata), params);\nimageOutputStream.close();\n\nmCompressedImageSize = outputStream.size();\n\ntry (FileOutputStream fileOutputStream = new FileOutputStream(mOutFileName)) {\n    fileOutputStream.write(outputStream.toByteArray());\n}\n\nmCompressedImage = ImageIO.read(new ByteArrayInputStream(outputStream.toByteArray()));\n
Run Code Online (Sandbox Code Playgroud)\n