java - 在java中编写JPEG时如何禁用压缩?

标签 java jpeg

我想将 JPEG 压缩为固定文件大小(20480 字节)。这是我的代码:

package io.github.baijifeilong.jpeg;

import lombok.SneakyThrows;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
import javax.imageio.stream.FileImageOutputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;

/**
 * Created by BaiJiFeiLong@gmail.com at 2019/10/9 上午11:26
 */
public class JpegApp {

    @SneakyThrows
    public static void main(String[] args) {
        BufferedImage inImage = ImageIO.read(new File("demo.jpg"));
        BufferedImage outImage = new BufferedImage(143, 143, BufferedImage.TYPE_INT_RGB);
        outImage.getGraphics().drawImage(inImage.getScaledInstance(143, 143, Image.SCALE_SMOOTH), 0, 0, null);

        JPEGImageWriteParam jpegParams = new JPEGImageWriteParam(null);
        jpegParams.setCompressionMode(ImageWriteParam.MODE_DISABLED);
        ImageWriter imageWriter = ImageIO.getImageWritersByFormatName("jpg").next();
        imageWriter.setOutput(new FileImageOutputStream(new File("demo-xxx.jpg")));
        imageWriter.write(null, new IIOImage(outImage, null, null), jpegParams);
    }
}

并且发生了错误:
Exception in thread "main" javax.imageio.IIOException: JPEG compression cannot be disabled
    at com.sun.imageio.plugins.jpeg.JPEGImageWriter.writeOnThread(JPEGImageWriter.java:580)
    at com.sun.imageio.plugins.jpeg.JPEGImageWriter.write(JPEGImageWriter.java:363)
    at io.github.baijifeilong.jpeg.JpegApp.main(JpegApp.java:30)

Process finished with exit code 1

那么如何禁用JPEG压缩呢?或者有什么方法可以通过任何压缩将任何图像压缩为固定文件大小?

最佳答案

至于最初的问题,如何创建未压缩的 jpeg:出于根本原因,不能。虽然我最初假设可以编写一个非压缩的 jpg 编码器产生输出,通过操作所涉及的哈夫曼树,可以使用任何现有的解码器进行解码,I had to dismiss it .霍夫曼编码只是相当多的转换管道的最后一步,不能跳过。 Custom Huffman trees也可能破坏不太复杂的解码器。

对于考虑到评论中的需求更改的答案(以您喜欢的任何方式调整大小和压缩,只需给我所需的文件大小),可以这样推理:

jpeg file specification定义图像结束标记。所以很有可能,事后修补零(或者只是任何东西)没有任何区别。将一些图像修补到特定大小的实验表明,gimp、chrome、firefox 和您的 JpegApp 毫无怨言地吞下了这样一个膨胀的文件。

为任何图像创建一个精确压缩到您的尺寸要求的压缩是相当复杂的(类型:对于图像 A,您需要 0.7143 的压缩比,对于图像 B 0.9356633,对于 C 12.445 ...)。有尝试predict image compression ratios不过,基于原始图像数据。

所以我建议只调整/压缩到任何小于 20480 的大小,然后修补它:

  • 根据原始jpg大小计算缩放比例和
    所需的大小,包括一个安全裕度来考虑
    问题本质上是模糊的
  • 以该比例调整图像大小
  • 修补缺失的字节以精确匹配所需的大小

  • 如此处所述
        private static void scaleAndcompress(String fileNameIn, String fileNameOut, Long desiredSize) {
        try {
            long size = getSize(fileNameIn);
    
            // calculate desired ratio for conversion to stay within size limit, including a safte maring (of 10%)
            // to account for the vague nature of the procedure. note, that this will also scale up if needed
            double desiredRatio = (desiredSize.floatValue() / size) * (1 - SAFETY_MARGIN);
    
            BufferedImage inImg = ImageIO.read(new File(fileNameIn));
            int widthOut = round(inImg.getWidth() * desiredRatio);
            int heigthOut = round(inImg.getHeight() * desiredRatio);
    
            BufferedImage outImg = new BufferedImage(widthOut, heigthOut, BufferedImage.TYPE_INT_RGB);
            outImg.getGraphics().drawImage(inImg.getScaledInstance(widthOut, heigthOut, Image.SCALE_SMOOTH), 0, 0, null);
    
            JPEGImageWriter imageWriter = (JPEGImageWriter) ImageIO.getImageWritersByFormatName("jpg").next();
    
            ByteArrayOutputStream outBytes = new ByteArrayOutputStream();
            imageWriter.setOutput(new MemoryCacheImageOutputStream(outBytes));
            imageWriter.write(null, new IIOImage(outImg, null, null), new JPEGImageWriteParam(null));
    
            if (outBytes.size() > desiredSize) {
                throw new IllegalStateException(String.format("Excess output data size %d for image %s", outBytes.size(), fileNameIn));
            }
            System.out.println(String.format("patching %d bytes to %s", desiredSize - outBytes.size(), fileNameOut));
            patch(desiredSize, outBytes);
            try (FileOutputStream outFileStream = new FileOutputStream(new File(fileNameOut))) {
                outBytes.writeTo(outFileStream);
            }
    
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    private static void patch(Long desiredSize, ByteArrayOutputStream bytesOut) {
        long patchSize = desiredSize - bytesOut.size();
        for (long i = 0; i < patchSize; i++) {
            bytesOut.write(0);
        }
    }
    
    private static long getSize(String fileName) {
        return (new File(fileName)).length();
    }
    
    private static int round(double f) {
        return Math.toIntExact(Math.round(f));
    }
    

    关于java - 在java中编写JPEG时如何禁用压缩?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58297626/

    相关文章:

    objective-c - UIImage UIImageJPEGRepresentation 对 UIImage 安全

    javascript - 如何将 PNG 转换为 JPG(客户端),然后再转换回 PNG(服务器端),同时保留全透明和半透明像素?

    r - 使用 R 将 jpg 转换为灰度 csv

    java - 如何识别 ItemWriter 中的异常(由 ItemReader、readItem 方法发生)

    java - RxJava - 按顺序上传文件 - 在调用 onNext 时发出下一个项目

    java - Netty 4.1 还需要 setUseClientMode 吗?

    java - ImageIO 无法将缓冲图像写入文件

    php - 如何从 MySQL 数据库加载多个 blob 图像

    java - 为自定义对象列表实现 removeAll 的问题

    java - Java 如何管理对数组元素的多线程访问?