之前在替 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 的方式來解決同樣的問題。