2012年10月29日 星期一

Java-image 高解析縮圖-ImageMagic 與 im4java 的蟹逅

最近在寫處理圖的程式, 在估哥的協助下, 找到了一套好用的程式!
ImageMagick + im4java (java jar)

如果你不喜歡用jdk提供的方式來轉圖, 縮圖, 切圖...等
可以使用這套, 還挺好懂的, im4java 官網的教學也寫的很清楚

ImageMagick 可以換成 GraphicsMagick , 其2種的差別就不在這裡說囉. 反正大家都說 GraphicsMagick 好, 但因為目前我是裝 ImageMagick 所以就以這個為例

PS: jmagick 本身的 setQuality 有問題, 這也是為什麼要改用 im4java 的原因 安裝方式 :

 - ImageMagick : http://www.imagemagick.org/script/binary-releases.php#windows
  32 or 64 的差別 (for windows)
  linux 的話, 看你系統是什麼. centos 的話就yum...等
 - download im4java.jar
  set it to your classpath,

使用時, 比較要注意的地方是
 - window 下, 要設定 ConvertCmd.setGlobalSearchPath(imageMagick 安裝路徑);
  但是在 linux 下則不用, 所以可以使用 jakarta 的 jar (lang.jar)來判斷是什麼 OS

  static {
    if (SystemUtils.IS_OS_WINDOWS) {
      ConvertCmd.setGlobalSearchPath("C:/Program Files/ImageMagick-6.8.0-Q8");
    }
  }

 下面提供範列的程式, 要注意的是有些是我自己用來動態產生 temp 圖要放哪裡的程式, 可以乎略, 另外處理圖時, 通常會丟一個 inputstream 來處理, 則必須去看一下 pipe 怎麼使用.
public class ImageUtils {
  static {
    if (SystemUtils.IS_OS_WINDOWS) {
      ConvertCmd.setGlobalSearchPath(ClubBuyConstants.IMAGEMAGICKPATH);
    }
  }
  private static final Logger log = LoggerFactory.getLogger(ImageUtils.class);

  /**
   * 判斷原來圖片的高,有沒有比要縮的高度還高
   * 
   * @param srcImage
   * @param width
   * @return
   * @throws IOException
   */
  public boolean checkResizeByHeight(String srcImage, int height)
      throws IOException {
    BufferedImage bf = ImageIO.read(new File(srcImage));
    int oriHeight = bf.getHeight();
    if (oriHeight <= height) {
      return false;
    }
    return true;
  }

  /**
   * 依寬縮放
   * 
   * @param oriIn
   * @param newWidth
   * @param toFileName
   * @throws IOException
   */
  public static void resizeImageWithNewWidth(File oriIn, int newWidth,
      String toFileName) throws IOException {
    resizeImageWithNewWidth(new FileInputStream(oriIn), newWidth, toFileName);
  }
  
  /**
   * 依寬縮放
   * 
   * @param oriIn 
   * @param newWidth 最長寬
   * @param toFileName
   * @throws IOException
   */
  public static void resizeImageWithNewWidth(InputStream oriIn, int newWidth,
      String toFileName) throws IOException {
    checkDirs(toFileName);
    FileOutputStream fout = null;
    try {
      IMOperation op = new IMOperation();
      op.addImage("-");

      // 取得來源寬與高
      BufferedImage image = ImageIO.read(oriIn);
      int imageWidth = image.getWidth();
      int imageHeight = image.getHeight();

      // 算出最後要縮的寬與高
      int currentWidth = 0;
      int currentHeight = 0;
      if (imageWidth < newWidth) {
        currentWidth = imageWidth;
        currentHeight = (imageWidth * imageHeight) / imageWidth;
      } else {
        currentWidth = newWidth;
        currentHeight = (newWidth * imageHeight) / imageWidth;
      }
      // 設定 quality(圖太小的話就設高一點, 以免不清楚)
      if (currentWidth <= 350 || currentHeight <= 350) {
        op.quality(95d);
      } else {
        op.quality(85d);
      }

      op.resize(currentWidth, currentHeight);
      op.addImage("jpg:-");

      fout = new FileOutputStream(new File(toFileName));
      ConvertCmd convert = new ConvertCmd();
      Pipe pipeIn = new Pipe(oriIn, null);
      Pipe pipeOut = new Pipe(null, fout);
      convert.setInputProvider(pipeIn);
      convert.setOutputConsumer(pipeOut);
      convert.run(op);
    } catch (InterruptedException e) {
      log.error(e.getMessage(), e);
    } catch (IM4JavaException e) {
      log.error(e.getMessage(), e);
    } finally {
      IOUtils.closeQuietly(oriIn);
      IOUtils.closeQuietly(fout);
    }
  }

  /**
   * 切圖, 並回傳 temp file, 後續自己處理
   * 
   * @param oriFile
   * @param x
   * @param y
   * @param toX
   * @param toY
   * @return
   * @throws Exception
   */
  public static File cropImgToTemp(File oriFile, int x, int y, int toX, int toY)
      throws Exception {
    return cropImgToTemp(new FileInputStream(oriFile), x, y, toX, toY);
  }
  
  /**
   * 切圖, 並回傳 temp file, 後續自己處理
   * 
   * @param oriIn 原始檔案
   * @param x 要切圖的 x 坐標(開始)
   * @param y 要切圖的 y 坐標(開始)
   * @param toX 要切圖的 x 坐標(結束)
   * @param toY 要切圖的  y 坐標(結束)
   * @return file (temp file)
   * @throws Exception
   */
  public static File cropImgToTemp(InputStream oriIn, int x, int y, int toX, int toY)
      throws Exception {

    String pk = DateFormatUtils.format(System.currentTimeMillis(), "S")
        + RandomStringUtils.randomAlphanumeric(2);
    ImageSaveBean isBean = ImageUtils.genImageSaveBean(EImgSaveType.TEMP, pk, ".jpg");
    checkDirs(isBean.getFullPath());

    FileOutputStream fout = null;
    try {
      IMOperation op = new IMOperation();
      op.addImage("-");
      op.quality(85d);

      BufferedImage image = ImageIO.read(oriIn);
      int imageWidth = image.getWidth();
      int imageHeight = image.getHeight();

      if (imageWidth < toX || imageHeight < toY) {
        throw new IllegalArgumentException("image width : " + imageWidth
            + ", image height : " + imageHeight);
      }
      // write image to file
      op.crop(toX, toY, x, y);
      op.addImage("jpg:-");
      fout = new FileOutputStream(new File(isBean.getFullPath()));
      ConvertCmd convert = new ConvertCmd();
      Pipe pipeIn = new Pipe(oriIn, null);
      Pipe pipeOut = new Pipe(null, fout);
      convert.setInputProvider(pipeIn);
      convert.setOutputConsumer(pipeOut);
      convert.run(op);
    } catch (InterruptedException e) {
      log.error(e.getMessage(), e);
    } catch (IM4JavaException e) {
      log.error(e.getMessage(), e);
    } finally {
      IOUtils.closeQuietly(oriIn);
      IOUtils.closeQuietly(fout);
    }

    return new File(isBean.getFullPath());
  }

  /**
   * 圖片可以先存一張 temp 檔, 透過下面的程式可以省略很多圖的問題(ex: 淘寶)
   * 
   * @param in 
   * @return
   * @throws IOException
   */
  public static File saveTempFile(InputStream in) throws IOException {

    String pk = DateFormatUtils.format(System.currentTimeMillis(), "S")
        + RandomStringUtils.randomAlphanumeric(2);
    ImageSaveBean isBean = ImageUtils.genImageSaveBean(EImgSaveType.TEMP, pk,
        ".jpg");
    checkDirs(isBean.getFullPath());
    String tempFile = isBean.getFullPath();
    FileOutputStream fout = null;
    try {

      IMOperation op = new IMOperation();
      op.addImage("-");
      op.quality(95d);
      op.addImage("jpg:-");

      fout = new FileOutputStream(new File(tempFile));
      ConvertCmd convert = new ConvertCmd();
      Pipe pipeIn = new Pipe(in, null);
      Pipe pipeOut = new Pipe(null, fout);
      convert.setInputProvider(pipeIn);
      convert.setOutputConsumer(pipeOut);
      convert.run(op);
    } catch (InterruptedException e) {
      log.error(e.getMessage(), e);
    } catch (IM4JavaException e) {
      log.error(e.getMessage(), e);
    } finally {
      IOUtils.closeQuietly(in);
      IOUtils.closeQuietly(fout);
    }
    return new File(tempFile);
  }

  /**
   * 暫存檔案
   * 
   * @param file
   * @return
   * @throws IOException
   */
  public static File saveTempFile(File file) throws IOException {
    return saveTempFile(FileUtils.openInputStream(file));
  }


  /**
   * 自動產生存圖的訊息
   * 
   * @param imgSaveType
   * @param entityPK
   *          可以寫 null
   * @param extension
   * @return
   */
  public static ImageSaveBean genImageSaveBean(EImgSaveType imgSaveType,
      String entityPK, String extension) {
    return ...
  }

  /**
   * 依 type 把圖的 http 路徑 gen 出來
   * 
   * @param enumImage
   * @param oriStr
   * @return
   */
  public static String getCurrentImgPath(EImgSaveType enumImage, String oriStr) {
    if (StringUtils.isBlank(oriStr)) {
      return null;
    }
    return getCurrentImgPath(enumImage, oriStr, -1);
  }

  /**
   * 依 type 把圖的 http 路徑 gen 出來
   * 
   * @param enumImage
   * @param oriStr
   * @return
   */
  public static String getCurrentImgPath(EImgSaveType enumImage, String oriStr,
      int width) {
    
    return ...
  }

  /**
   * 判斷目錄在不在, 不在就建立
   * 
   * @param path
   */
  public static void checkDirs(String path) {
    File containerDir = new File(path);
    if (!containerDir.getParentFile().isDirectory()) {
      containerDir.getParentFile().mkdirs();
    }
  }

 大概是這個樣子, 速度上挺快的, 又能解決一些格式上的問題, 如淘寶的圖抓下來後, 只要一透過 ImageIO.read 就會多出一層透明層的鳥問題...等.

im4java 就是把 imageMagick 給包起來, 像我們自己寫 Runtime.getRuntime().exec(commandText); 一樣, 可以把 op 印出來看看 System.out.println(op);
當然還有很多很好用的功能, 像浮水印, 可以把自己家的 url 給印上去...等.

  IMOperation op = new IMOperation();
  ....  
  // gravity : southeast 右下角, 
  op.font("Arial").gravity("southeast").pointsize(18).fill("#BCBFC8").draw("text 5,5 www.我是小胖.com");
      ...

沒有留言:

張貼留言