之前在替 DinBenDon 做產品縮圖時,美食照片縮小後都變得麻麻花花的。而最近剛 beta 的 diggirl.net,也是用 java 平台的,它的縮圖美女也都生了不少的刺在上邊。Java 縮圖到底出了什麼問題啊?
如果你上網找 Java 改變圖大小的作法,你大概會發現人人都建議使用新的 AffineTransform 來實做:
public BufferedImage resize(BufferedImage original, double scale) { AffineTransform affineTransform = new AffineTransform(); affineTransform.scale(scale, scale); RenderingHints hints = new RenderingHints( RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); AffineTransformOp affineTransformOp = new AffineTransformOp( affineTransform, hints); int newWidth = new Double(original.getWidth() * scale).intValue(); int newHeight = new Double(original.getHeight() * scale).intValue(); return affineTransformOp.filter(original, new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB)); }
按文件上的解釋,AffineTransform 是一 2D 坐標到其他 2D 坐標的線性映射,所以可以做各種形狀的變化,當然這也包括了放大和縮小。如上面的程式片段所示,成像時所用的補強是 INTERPOLATION,即內插法 -- 當原像素不足時,擇週圍幾點採內插法計算 (可能是依二次或三次方程式)。
圖放大時,用內插算中間不足的像素是合理的,但遇到縮小時,內插運算反而幫了倒忙,常常算過頭,讓相片看起來毛毛刺刺的。縮小的問題是像素過多,應該取多的像素點的平均值才是合理的計算法:
public BufferedImage reduce(final BufferedImage original, double scale) { final int w = new Double(original.getWidth() * scale).intValue(); final int h = new Double(original.getHeight() * scale).intValue(); final Image rescaled = original.getScaledInstance(w, h, Image.SCALE_AREA_AVERAGING); final BufferedImage result = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); final Graphics2D g = result.createGraphics(); g.drawImage(rescaled, 0, 0, null); g.dispose(); return result; }
上面就不用 AffineTransform 了,而是使用舊的 AWT 的函式,搭配平均值 -- Image.SCALE_AREA_AVERAGING 來計算縮圖。這一次縮小的圖就圓滑多了,不會毛毛燥燥的囉。
Updated:OnJava 裡面也提到一些 multimedia 的作法。看內容有提到 ImageMagick/JMagick JNI 的方式來解決同樣的問題。