Java Servlet 圖片處理專案實作 (二)

自定切割壓縮圖片,壓縮為 Zip 回傳給使用者。

java image project
自定切割壓縮圖片 (ZipImage)

規格

可接受使用者輸入要切割的多組圖片大小, 接受兩組參數:

resize輸入格式預期為 ["60x60", "120x200"] 這種格式。 若是沒有輸入 resize 參數,系統預設幫使用者切割為 ["100x100", "250x250", "600x600"] 三個 size

imageFile: 圖片檔案

處理完成後回傳一個 zip 檔案給使用者,zip 檔名格式為 {圖片名稱}{寬}x{高}.{format}

ex.

# zip-image.zip
./image100x100.jpg
./image250x250.jpg
./image600x600.jpg

ok! 咱們開始吧!


簡易 HTML 表單 ZipImage.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>自定切割壓縮圖片</title>
</head>
<body>
<form
  action="zip-image"
  method="POST"
  enctype="multipart/form-data"
>
    <p>選擇圖片</p>
    <input type="file" name="imageFile">
    <p>指定尺寸</p>
    <input type="text" name="resize">
    <p>指定尺寸</p>
    <input type="text" name="resize">
    <p>指定尺寸</p>
    <input type="text" name="resize">

    <button type="submit">送出</button>
</form>
</body>
</html>

圖片寬高資訊物件 ResizeInfo.java

/**
 * 要輸出的圖片寬高資訊
 */
class ResizeInfo {
    // 要輸出的寬
    public int m_width;
    // 要輸出的高
    public int m_height;

    /**
     * @param sSize {String} - 字串通常是 60x60 120x200 這種格式
     */
    ResizeInfo(String sSize) {
        this(sSize.split("x"));
    }

    private ResizeInfo(String[] spSize) {
        m_width = Integer.parseInt(spSize[0]);
        m_height = Integer.parseInt(spSize[1]);
    }

    @Override
    public String toString() {
        return m_width + "x" + m_height;
    }
}

因為只有 ResizeInfo.java 稍微有點不好用, 我額外做了一個工廠方法物件來幫忙產生 ResizeInfo 物件 (因為不是這次的主題,稍微看一看就好!不用太執著在這上面) ResizeInfoFactory.java

/**
 * @author wayne on 2020/4/10
 */
public class ResizeInfoFactory {
    /**
     * @param sizeArr - 接收到使用者指定的圖片尺寸陣列
     * @return - 沒有的話回傳 Optional.empty()
     */
    public static Optional<List<ResizeInfo>> getResizeInfo(String[] sizeArr) {
        /**
         * 例外判斷
         */
        if (sizeArr == null || sizeArr[0].isEmpty()) {
            return Optional.empty();
        }
        List<String> sizeList = Arrays.asList(sizeArr); // 字串轉為 List 結構
        List<ResizeInfo> resizeInfoList = getResizeInfo$(sizeList);
        return Optional.of(resizeInfoList);
    }

    public static List<ResizeInfo> defaultResizeInfo() {
        List<String> defaultSizeList = Arrays.asList("100x100", "250x250", "600x600");
        return getResizeInfo$(defaultSizeList);
    }

    /**
     * 物件內部的轉換方法,不公開給外部使用
     * $ 符號代表直接取得,不處理例外
     * @return 處理陣列字串轉為陣列 ResizeInfo 物件
     */
    private static List<ResizeInfo> getResizeInfo$(List<String> sizeList) {
        return sizeList.stream()
                .map(sizeItem -> new ResizeInfo(sizeItem))
                .collect(Collectors.toList())
                ;
    }
}

Servlet 物件 ZipImage.java

@MultipartConfig() // 這個是為了要使用 getPart() 方法一定要加的哦!
@WebServlet(
        name = "ZipImage",
        urlPatterns = {"/zip-image"}, // servlet entry url
        loadOnStartup = 1
)
public class ZipImage extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        // 設定回應格式
        resp.setContentType("application/zip");
        // Servlet 輸出流,用於輸出檔案給用戶
        ServletOutputStream out = resp.getOutputStream();
        ZipOutputStream zipOut = new ZipOutputStream(out); // 用檔案輸出流建立出 Zip 輸出流

        // 處理使用者傳入想要壓縮的 size
        String[] sizeArr = req.getParameterMap().get("resize"); // {接收使用者指定的圖片尺寸},收到的會是陣列格式
        List<ResizeInfo> resizeInfoList = ResizeInfoFactory
                .getResizeInfo(sizeArr)
                .orElse(ResizeInfoFactory.defaultResizeInfo()) // 如果使用者未指定尺寸的話,回傳預設的切割 size
                ;

        Part imagePart = req.getPart("imageFile"); // 取得使用者上傳的圖片檔案

        String fileName = imagePart.getSubmittedFileName(); // 使用者上傳的檔名
        String formatName = fileName.substring(fileName.lastIndexOf(".") + 1); // 副檔名(拿原圖的副檔名就好)

        /**
         * 把所有 ResizeInfo 物件迭代一次
         */
        resizeInfoList.stream().forEach(resizeInfo -> {
            ZipEntry zipEntry = new ZipEntry(fileName + resizeInfo.toString() + "." + formatName);
            try {
                BufferedImage bufferedImage = ImageIO.read(imagePart.getInputStream());
                // 建立一個空白的 BufferedImage 物件,
                // 寬度、高度 為使用者輸入的
                // 輸出類型為使用者上傳的圖片類型
                BufferedImage exportBFImage = new BufferedImage(resizeInfo.m_width, resizeInfo.m_height, bufferedImage.getType());

                // 用剛才建立的空白 BufferedImage 物件來建立畫布
                Graphics2D g2d = exportBFImage.createGraphics();
                g2d.drawImage(
                        bufferedImage, // 把我們讀入的圖片畫上去
                        0, // x軸起始點
                        0, // y軸起始點
                        resizeInfo.m_width, // 要畫上去的寬度
                        resizeInfo.m_height, // 要畫上去的長度
                        null
                );
                g2d.dispose(); // g2d 就不再接受被寫入內容

                zipOut.putNextEntry(zipEntry);
                ImageIO.write(exportBFImage, formatName, zipOut);

            } catch (IOException e) {
                e.printStackTrace();
            }
        });

        // 關閉輸出流
        zipOut.close();
        out.close();

    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // "/" 開頭帶表從我們 Smart Tomcat 定義的 Deployment Directory 開始
        // 也就是 webapp 那個目錄找檔案
        RequestDispatcher view = req.getRequestDispatcher("/ZipImage.html");

        // 轉回去給使用者
        view.forward(req, resp);
    }
}

這次專案的程式碼一樣上 github 了! 歡迎拉下來看看!

https://github.com/judysocute/image-resize