01 February 2007

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