【问题标题】:Custom quantization tables for JPEG compression in JavaJava中JPEG压缩的自定义量化表
【发布时间】:2014-07-30 12:46:07
【问题描述】:

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

Quantization table 0x00 was not defined

这就是我的代码的样子:

        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()));

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

谢谢, R.

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

更新 2:因此在实际调试执行之后,我发现如果在参数对象中设置表,则不会为量化和 Huffman 表生成元数据。如果元数据丢失,则表不会写入文件中。问题是我看不到自定义元数据内容的方法。

【问题讨论】:

    标签: java jpeg quantization


    【解决方案1】:

    非常有趣的问题,不幸的是,这不是微不足道的......这是我发现的:

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

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

    距离JPEG Metadata Format Specification and Usage Notes更远:

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

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

    从上面提到的同一个文档中,元数据中的表将被忽略并替换,除非压缩模式是MODE_COPY_FROM_METADATA,所以我们需要指定它。

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

    这是我想出的代码:

    // Obtain qtables
    mQMatrix = ...;
    
    // Read source image
    ImageInputStream imageInputStream = ImageIO.createImageInputStream(...);
    ImageReader reader = ImageIO.getImageReaders(imageInputStream).next();
    reader.setInput(imageInputStream);
    
    mSourceImage = reader.read(0);
    IIOMetadata metadata = null;
    
    // We need the imageWriter to create the default JPEG metadata
    ImageWriter imageWriter = ImageIO.getImageWritersByFormatName("JPEG").next();
    
    if (mQMatrix != null) {
        dumpMatrices(mQMatrix);
    
        // Obtain default image metadata data, in native JPEG format
        metadata = imageWriter.getDefaultImageMetadata(ImageTypeSpecifier.createFromRenderedImage(m‌​SourceImage), null);
        IIOMetadataNode nativeMeta = (IIOMetadataNode) metadata.getAsTree("javax_imageio_jpeg_image_1.0");
    
        // Update dqt to values from mQMatrix
        NodeList dqtables = nativeMeta.getElementsByTagName("dqtable");
        for (int i = 0; i < dqtables.getLength(); i++) {
            IIOMetadataNode dqt = (IIOMetadataNode) dqtables.item(i);
            int dqtId = Integer.parseInt(dqt.getAttribute("qtableId"));
            dqt.setUserObject(mQMatrix[dqtId]);
        }
    
        // For some reason, we need dht explicitly defined, when using MODE_COPY_FROM_METADATA...
        NodeList dhtables = nativeMeta.getElementsByTagName("dhtable");
    
        // Just use defaults for dht
        JPEGHuffmanTable[] huffmanDcTables = {JPEGHuffmanTable.StdDCLuminance, JPEGHuffmanTable.StdDCChrominance};
        JPEGHuffmanTable[] huffmanAcTables = {JPEGHuffmanTable.StdACLuminance, JPEGHuffmanTable.StdACChrominance};
    
        // Update dht
        for (int i = 0; i < dhtables.getLength(); i++) {
            IIOMetadataNode dht = (IIOMetadataNode) dhtables.item(i);
            int dhtClass = Integer.parseInt(dht.getAttribute("class")); // 0: DC, 1: AC
            int dhtId = Integer.parseInt(dht.getAttribute("htableId"));
    
            dht.setUserObject(dhtClass == 0 ? huffmanDcTables[dhtId] : huffmanAcTables[dhtId]);
        }
    
        // Merge updated tree back (important!)
        metadata.mergeTree("javax_imageio_jpeg_image_1.0", nativeMeta);
    }
    
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    ImageOutputStream imageOutputStream = ImageIO.createImageOutputStream(outputStream);
    imageWriter.setOutput(imageOutputStream);
    
    // See http://docs.oracle.com/javase/6/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html#tables
    JPEGImageWriteParam params = new JPEGImageWriteParam(null);
    params.setCompressionMode(metadata == null ? MODE_DEFAULT : MODE_COPY_FROM_METADATA); // Unless MODE_COPY_FROM_METADATA, tables will be created!
    
    imageWriter.write(null, new IIOImage(mSourceImage, null, metadata), params);
    imageOutputStream.close();
    
    mCompressedImageSize = outputStream.size();
    
    try (FileOutputStream fileOutputStream = new FileOutputStream(mOutFileName)) {
        fileOutputStream.write(outputStream.toByteArray());
    }
    
    mCompressedImage = ImageIO.read(new ByteArrayInputStream(outputStream.toByteArray()));
    

    【讨论】:

    • 我认为它与元数据有关,但我对 JPEG 格式并不熟悉。谢谢!代表好赚! :)
    • 就个人而言,我认为 ImageIO 元数据 API 不直观且难以使用。但它通常可以完成工作。不管怎样,谢谢! :-)
    • 所以我开始实施这个,我有一个小的修正:如果你想打开任何类型的文件,你不能从阅读器那里获得原生 jpeg 元数据(我主要是尝试使用原始 bmp/tiff/png 文件)。所以我必须加载默认的 jpeg 元数据并将新节点与其合并:“IIOMetadata metadata = imageWriter.getDefaultImageMetadata(ImageTypeSpecifier.createFromRenderedImage(mSourceImage), new JPEGImageWriteParam(null));”
    • @rhobincu 我修复了代码,类似于您建议的编辑(它被某种方式拒绝了......)。
    猜你喜欢
    • 1970-01-01
    • 2012-05-16
    • 1970-01-01
    • 2011-08-09
    • 2017-11-09
    • 1970-01-01
    • 1970-01-01
    • 2015-05-26
    • 1970-01-01
    相关资源
    最近更新 更多