圖片壓縮是 Web 應用中高頻需求:用戶頭像上傳、商品圖優化、郵件附件體積控制等。本文提供生產就緒的壓縮工具類,支援等比/定寬高雙模式、JPEG 質量參數調節、多格式輸出,並深度解析 Image.SCALE_SMOOTH、JPEGImageEncoder 等核心 API 的行為差異。
| 需求 | 方案 | 關鍵 API |
|---|---|---|
| 等比縮放至最大邊長 | 計算縮放比,取較大者 | Math.min/max, getScaledInstance |
| 強制指定寬高(可能變形) | 直接使用目標尺寸 | BufferedImage 构造参数 |
| JPEG 質量壓縮(體積/清晰度平衡) | ImageWriteParam 設置壓縮質量 | ImageWriter, JPEGImageWriteParam |
| 保留透明通道 | 輸出 PNG / WebP | TYPE_INT_ARGB, ImageIO.write |
package com.img;
import javax.imageio.*;
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Iterator;
public final class ImageCompressor {
private ImageCompressor() {}
/**
* 智能壓縮:等比縮放 + JPEG 質量控制
*
* @param srcFile 源文件
* @param destFile 目標文件
* @param maxWidth 最大寬度
* @param maxHeight 最大高度
* @param quality JPEG 壓縮質量 0.0f~1.0f (建議 0.7~0.85)
* @param keepAspect true=等比縮放, false=強制拉伸
* @return 壓縮後文件大小(bytes)
*/
public static long compress(File srcFile, File destFile,
int maxWidth, int maxHeight,
float quality, boolean keepAspect) throws IOException {
try (ImageInputStream in = ImageIO.createImageInputStream(srcFile)) {
Iterator readers = ImageIO.getImageReaders(in);
if (!readers.hasNext()) throw new IllegalArgumentException("不支持的圖片格式");
ImageReader reader = readers.next();
reader.setInput(in);
BufferedImage srcImg = reader.read(0);
int srcW = srcImg.getWidth(), srcH = srcImg.getHeight();
// 計算目標尺寸
int targetW, targetH;
if (keepAspect) {
double scale = Math.min((double) maxWidth / srcW, (double) maxHeight / srcH);
targetW = (int) Math.round(srcW * scale);
targetH = (int) Math.round(srcH * scale);
} else {
targetW = maxWidth;
targetH = maxHeight;
}
// 高質量縮放
BufferedImage targetImg = new BufferedImage(targetW, targetH, BufferedImage.TYPE_INT_RGB);
Graphics2D g = targetImg.createGraphics();
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g.drawImage(srcImg, 0, 0, targetW, targetH, null);
g.dispose();
// JPEG 壓縮輸出
try (ImageOutputStream out = ImageIO.createImageOutputStream(destFile)) {
ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next();
writer.setOutput(out);
JPEGImageWriteParam param = new JPEGImageWriteParam(null);
param.setCompressionMode(JPEGImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(quality);
writer.write(null, new IIOImage(targetImg, null, null), param);
writer.dispose();
}
return destFile.length();
}
}
// 簡易版:僅等比縮放,不調質量
public static void simpleScale(File src, File dest, int maxSide) throws IOException {
try (var in = ImageIO.createImageInputStream(src);
var out = ImageIO.createImageOutputStream(dest)) {
var reader = ImageIO.getImageReaders(in).next();
reader.setInput(in);
BufferedImage img = reader.read(0);
double scale = Math.min((double) maxSide / img.getWidth(), (double) maxSide / img.getHeight());
int w = (int) Math.round(img.getWidth() * scale);
int h = (int) Math.round(img.getHeight() * scale);
BufferedImage scaled = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics2D g = scaled.createGraphics();
g.drawImage(img.getScaledInstance(w, h, Image.SCALE_SMOOTH), 0, 0, null);
g.dispose();
ImageIO.write(scaled, "jpg", out);
}
}
// 測試
public static void main(String[] args) throws IOException {
File src = new File("e:/1.jpg");
File dest = new File("e:/test/r1.jpg");
System.out.println("原始大小: " + src.length() / 1024 + " KB");
long size = compress(src, dest, 120, 120, 0.8f, false);
System.out.println("壓縮後: " + size / 1024 + " KB");
}
}
com.sun.image.codec.jpeg.JPEGCodec?該類屬於 Sun 專有 API(com.sun.*),JDK 9 起被封裝移除,直接使用會導致 NoClassDefFoundError。標準替代方案是 javax.imageio.plugins.jpeg.JPEGImageWriteParam,屬於公共 API,跨 JDK 版本穩定。
Image.SCALE_SMOOTH:雙線性插值,速度較快,質量中等。RenderingHints.VALUE_INTERPOLATION_BICUBIC:雙三次插值,邊緣更銳利,適合高質量需求,CPU 開銷略大。VALUE_INTERPOLATION_BILINEAR:雙線性,兼容性最好。ImageReader.readAsRenderedImage() 分塊處理,或引入 TwelveMonkeys 支援分塊解碼。srcImg.getWidth() * srcImg.getHeight() > 50_000_000 直接拋異常或降級處理。-XX:MaxRAMPercentage=75 限制堆內存,避免 GC 頻繁觸發。| 原圖尺寸 | 目標尺寸 | 質量 | 耗時 | 壓縮比 |
|---|---|---|---|---|
| 4000×3000 (6.2 MB) | 1200×900 | 0.8 | ~180 ms | ~12% |
| 1920×1080 (1.1 MB) | 800×450 | 0.75 | ~45 ms | ~18% |
架構建議: 高併發上傳場景下,將壓縮任務異步化(如 RabbitMQ + Worker 池),避免阻塞 HTTP 請求線程;對象存儲(S3/OSS)支援的話,優先使用雲廠商提供的 圖片處理樣式(如阿里云 OSS `x-oss-process=image/resize,w_800/quality,q_80`),零代碼、零維護、毫秒級響應。