簡體   English   中英

使用 BufferedImage 和 ImageIO 將其轉換為 byte[] 后,圖像大小變小

[英]Image size getting decreased after converting it into byte[] using BufferedImage and ImageIO

我正在使用以下代碼將圖像轉換為字節 []。

public static byte[] extractBytes (String ImageName) throws IOException {

   ByteArrayOutputStream baos=new ByteArrayOutputStream();
        BufferedImage img=ImageIO.read(new File(ImageName));
        ImageIO.write(img, "jpg", baos);
        return baos.toByteArray();
    }

現在當我測試我的代碼時:

public static void main(String[] args) throws IOException {
    String filepath = "image_old.jpg";
    File outp=new File(filepath);
    System.out.println("Size of original image="+outp.length());
    byte[] data = extractBytes(filepath);
    System.out.println("size of byte[] data="+data.length);
    BufferedImage img = ImageIO.read(new ByteArrayInputStream(data));
    //converting the byte[] array into image again
    File outputfile = new File("image_new.jpg");
    ImageIO.write(img, "jpeg", outputfile);
    System.out.println("size of converted image="+outputfile.length());
}

我得到了非常奇怪的結果:

Size of original image=78620 
size of byte[] data=20280
size of converted image=20244

將圖像轉換為 byte[] 后,它的大小減少了大約 1/4,而且當我將 byte[] 轉換回圖像時,它的大小會改變。但輸出圖像已成功在所需位置創建。 放大 500-600% 后,我可以看到原始圖像和新圖像的質量略有不同。 放大后新圖像有點模糊。

這是我正在進行測試的圖像http://pbrd.co/1BrOVbf

請解釋這種尺寸變化的原因,我還想知道在此之后獲得相同尺寸的任何方法。

您擁有的圖像以最高質量設置(ImageIO 術語中的“100%”或1.0 )壓縮。 JPEG 壓縮在如此高的設置下不是很有效,因此比平常大很多。 使用ImageIO.write(..., "JPEG", ...) ,將使用默認質量設置。 此默認值為0.75 (該值的確切含義取決於編碼器,但不是精確的科學),因此質量較低,文件大小較小。

(原始圖像和重新壓縮圖像之間文件大小顯着減小的另一個可能原因是元數據的刪除。使用ImageIO.read(file)讀取時,您實際上是在剝離 JPEG 文件中的任何元數據, 如 XMP、Exif 或 ICC 配置文件。在極端情況下(是的,我在這里主要談論 Photoshop ;-))此元數據可能占用比圖像數據本身更多的空間(即,可能有幾兆字節的元數據) . 但是,對於您的文件,情況並非如此。)

正如您從第二次重新壓縮(從byte[]到最終輸出文件)中看到的,輸出僅略小於輸入。 這是因為質量設置(未指定,因此仍使用默認值)在兩種情況下都相同(此外,在此步驟中任何元數據也將丟失,因此不會增加文件大小)。 微小的差異可能是由於 JPEG 解壓縮/重新壓縮中的一些小損失(舍入錯誤等)造成的。

雖然有點違反直覺,但在重新壓縮 JPEG 時,最少的數據丟失(就原始圖像的變化而言,而不是文件大小),總是通過使用相同質量設置(使用完全相同的表應該實際上是無損的,但可能仍會出現小的舍入錯誤)作為原始。 增加質量設置會使文件輸出更大,但實際上質量會下降。

100% 確保不丟失任何數據或圖像質量的唯一方法是首先不解碼/編碼圖像,而是逐字節復制文件,例如:

File in = ...;
File out = ...;

InputStream input = new FileInputStream(in);
try {
    OutputStream output = new FileOutputStream(out);
    try {
        copy(input, output);
    }
    finally {
        output.close();
    }
}
finally {
    input.close();
}

和復制方法:

public void copy(final InputStream in, final OutputStream out) {
    byte[] buffer = new byte[1024]; 
    int count;

    while ((count = in.read(buffer)) != -1) {
        out.write(buffer, 0, count);
    }

    // Flush out stream, to write any remaining buffered data
    out.flush();
}

當你調用 ImageIO.write(img, "jpeg", outputfile); ImageIO 庫使用自己的壓縮參數寫入 jpeg 圖像。 輸出圖像似乎比輸入圖像壓縮得更多。 您可以通過更改下面對 jpegParams.setCompressionQuality 的調用中的參數來調整壓縮級別。 生成的文件可能比原始文件更大或更小,具體取決於每個文件的相對壓縮級別。

public static ImageWriter getImageWriter() throws IOException {
    IIORegistry registry = IIORegistry.getDefaultInstance();
    Iterator<ImageWriterSpi> services = registry.getServiceProviders(ImageWriterSpi.class, (provider) -> {
        if (provider instanceof ImageWriterSpi) {
            return Arrays.stream(((ImageWriterSpi) provider).getFormatNames()).anyMatch(formatName -> formatName.equalsIgnoreCase("JPEG"));
        }
        return false;
    }, true);
    ImageWriterSpi writerSpi = services.next();
    ImageWriter writer = writerSpi.createWriterInstance();
    return writer;
}

public static void main(String[] args) throws IOException {
    String filepath = "old.jpg";
    File outp = new File(filepath);
    System.out.println("Size of original image=" + outp.length());
    byte[] data = extractBytes(filepath);
    System.out.println("size of byte[] data=" + data.length);
    BufferedImage img = ImageIO.read(new ByteArrayInputStream(data));
    File outputfile = new File("new.jpg");
    JPEGImageWriteParam jpegParams = new JPEGImageWriteParam(null);
    jpegParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
    jpegParams.setCompressionQuality(1f);
    ImageWriter writer = getImageWriter();
    outputfile.delete();
    try (final ImageOutputStream stream = createImageOutputStream(outputfile)) {
        writer.setOutput(stream);
        try {
            writer.write(null, new IIOImage(img, null, null), jpegParams);
        } finally {
            writer.dispose();
            stream.flush();
        }
    }
    System.out.println("size of converted image=" + outputfile.length());
}

此解決方案改編自 JeanValjean 在此處給出的答案Setting jpg compression level with ImageIO in Java

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM