首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 圖片裁剪工具類:ImageIO 實現任意區域裁剪與格式保持

     編輯:Java 相關知識

前言

圖片裁剪是頭像上傳、文檔處理、縮圖生成等場景的高頻需求。Java 標準庫 javax.imageio 提供零依賴的圖片讀寫與區域裁剪能力。本文封裝通用工具類,支援任意矩形區域裁剪、多格式輸出、記憶體安全與異常處理。

核心原理:ImageReadParam 設定源區域

ImageReadParam param = reader.getDefaultReadParam();
Rectangle rect = new Rectangle(x, y, width, height); // 裁剪區域
param.setSourceRegion(rect); // 僅讀取指定區域,節省記憶體
BufferedImage cropped = reader.read(0, param);

優勢:不需載入完整圖片到記憶體,直接解碼目標區域,大圖處理效能優異。

通用工具類完整實作

package com.example.util;

import javax.imageio.*;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Iterator;

public final class ImageCropUtil {
    private ImageCropUtil() {}

    /**
     * 裁剪圖片並保存
     * @param x 左上角 X 座標
     * @param y 左上角 Y 座標
     * @param width 裁剪寬度
     * @param height 裁剪高度
     * @param srcPath 源圖片路徑
     * @param destPath 目標保存路徑
     * @throws IOException 讀寫異常
     */
    public static void crop(int x, int y, int width, int height, 
                            String srcPath, String destPath) throws IOException {
        
        File srcFile = new File(srcPath);
        if (!srcFile.exists()) throw new FileNotFoundException("源文件不存在: " + srcPath);
        
        String format = getFormatName(srcPath);
        if (format == null) throw new IOException("無法識別圖片格式: " + srcPath);

        try (FileInputStream fis = new FileInputStream(srcFile);
             ImageInputStream iis = ImageIO.createImageInputStream(fis)) {
            
            // 1. 獲取對應格式的 Reader
            Iterator readers = ImageIO.getImageReadersByFormatName(format);
            if (!readers.hasNext()) throw new IOException("無可用解碼器: " + format);
            ImageReader reader = readers.next();
            
            try {
                reader.setInput(iis, true); // true = 僅向前讀取,節省記憶體
                
                // 2. 設定裁剪區域
                ImageReadParam param = reader.getDefaultReadParam();
                Rectangle rect = new Rectangle(x, y, width, height);
                
                // 邊界檢查:防止越界導致異常
                int imgWidth = reader.getWidth(0);
                int imgHeight = reader.getHeight(0);
                if (x < 0 || y < 0 || x + width > imgWidth || y + height > imgHeight) {
                    throw new IllegalArgumentException(
                        String.format("裁剪區域超出圖片邊界: 圖片[%dx%d] 裁剪[%d,%d,%dx%d]", 
                            imgWidth, imgHeight, x, y, width, height));
                }
                
                param.setSourceRegion(rect);
                
                // 3. 讀取裁剪後的圖片
                BufferedImage cropped = reader.read(0, param);
                if (cropped == null) throw new IOException("裁剪結果為空");
                
                // 4. 寫入目標文件
                File destFile = new File(destPath);
                destFile.getParentFile().mkdirs(); // 確保目錄存在
                boolean written = ImageIO.write(cropped, format, destFile);
                if (!written) throw new IOException("無對應編碼器寫入格式: " + format);
                
            } finally {
                reader.dispose(); // 釋放原生資源
            }
        }
    }

    /**
     * 裁剪並返回 BufferedImage(不寫文件,供後續處理)
     */
    public static BufferedImage cropToImage(int x, int y, int width, int height, String srcPath) 
            throws IOException {
        File srcFile = new File(srcPath);
        String format = getFormatName(srcPath);
        
        try (FileInputStream fis = new FileInputStream(srcFile);
             ImageInputStream iis = ImageIO.createImageInputStream(fis)) {
            
            Iterator readers = ImageIO.getImageReadersByFormatName(format);
            ImageReader reader = readers.next();
            try {
                reader.setInput(iis, true);
                ImageReadParam param = reader.getDefaultReadParam();
                param.setSourceRegion(new Rectangle(x, y, width, height));
                return reader.read(0, param);
            } finally {
                reader.dispose();
            }
        }
    }

    /** 從文件名/路徑推斷格式名 */
    private static String getFormatName(String path) {
        int dot = path.lastIndexOf('.');
        if (dot > 0 && dot < path.length() - 1) {
            return path.substring(dot + 1).toLowerCase();
        }
        return null;
    }

    // --- 使用示例 ---
    public static void main(String[] args) {
        try {
            // 裁剪並保存
            ImageCropUtil.crop(100, 50, 400, 300, "input.jpg", "output_crop.jpg");
            System.out.println("裁剪完成");
            
            // 裁剪後進一步處理(如縮放、水印)
            BufferedImage img = ImageCropUtil.cropToImage(0, 0, 200, 200, "logo.png");
            // ImageIO.write(img, "png", new File("logo_200.png"));
            
        } catch (IOException e) {
            System.err.println("裁剪失敗: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

進階功能擴展

1. 圓形裁剪(頭像常用)

public static BufferedImage cropCircle(BufferedImage src, int x, int y, int diameter) {
    BufferedImage output = new BufferedImage(diameter, diameter, BufferedImage.TYPE_INT_ARGB);
    Graphics2D g2 = output.createGraphics();
    
    // 抗鋸齒
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    
    // 繪製圓形裁剪區域
    Shape circle = new Ellipse2D.Double(0, 0, diameter, diameter);
    g2.setClip(circle);
    g2.drawImage(src, -x, -y, null);
    g2.dispose();
    
    return output;
}

2. 等比縮放 + 居中裁剪(縮圖生成)

public static BufferedImage resizeAndCropCenter(BufferedImage src, int targetW, int targetH) {
    double srcRatio = (double) src.getWidth() / src.getHeight();
    double dstRatio = (double) targetW / targetH;
    
    int cropW, cropH, cropX, cropY;
    if (srcRatio > dstRatio) { // 源圖較寬,上下裁剪
        cropH = src.getHeight();
        cropW = (int) (cropH * dstRatio);
        cropX = (src.getWidth() - cropW) / 2;
        cropY = 0;
    } else { // 源圖較高,左右裁剪
        cropW = src.getWidth();
        cropH = (int) (cropW / dstRatio);
        cropX = 0;
        cropY = (src.getHeight() - cropH) / 2;
    }
    
    BufferedImage cropped = src.getSubimage(cropX, cropY, cropW, cropH);
    return scaleImage(cropped, targetW, targetH);
}

private static BufferedImage scaleImage(BufferedImage src, int w, int h) {
    BufferedImage dst = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
    Graphics2D g2 = dst.createGraphics();
    g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
    g2.drawImage(src, 0, 0, w, h, null);
    g2.dispose();
    return dst;
}

效能與記憶體優化建議

場景建議
大圖裁剪(>50MB)使用 ImageReadParam.setSourceRegion 僅解碼目標區域,避免全圖載入
批量處理複用 ImageReader、ImageInputStream;處理完立即 dispose()
高併發服務引入 TwelveMonkeys ImageIO 插件支援更多格式;考慮異步隊列削峰
記憶體受限環境設置 -Djavax.imageio.maxPixels=... 限制最大像素;使用 DiskCacheImageInputStream

常見格式支援說明

  • JPEG:無透明度,有損壓縮,寫入時可設置品質參數
  • PNG:支援透明度,無損壓縮,適合 Logo/圖標
  • GIF:支援動畫,256 色,簡單動圖
  • BMP/WBMP:無壓縮,文件大,少用
  • TIFF:專業印刷,多頁,需 TwelveMonkeys 插件
  • WebP/HEIF:現代高效格式,需額外插件

總結

基於 ImageIO + ImageReadParam 的裁剪方案零依賴、記憶體友好、支援主流格式。工具類封裝了邊界檢查、資源管理、格式自動識別,可直接用於生產。進階場景(圓形裁剪、智能縮圖、濾鏡)可組合 Graphics2D 實現。超大圖/高併發建議引入 TwelveMonkeys 或遷移至 Thumbnailator/ImgLib2 等專業庫。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved