我們希望在將圖片旋轉後,能得到能夠剛好容納旋轉後圖片的新片大小,
例如以下的例子,上面的是原圖,下面的是順時鏡旋轉了60度的圖片,
可以看得出來,在此例中,旋轉後的圖其寬高比原圖還要大,原因是為了要容納旋轉後的圖,新圖片的寬高須要重新計算的關係。
在看程式碼之前,我們需要計算旋轉後的新寬高,
首先,例如有一個如下圖所示的,寬為w,高為h的圖片
下圖是旋轉了Θ角後的圖片,並附上了新寬(w')及新高(h')的推導
w'= h |sin(θ)| + w |cos(θ)|
h' = h |cos(θ)| + w |sin(θ)|
因為Java繪圖會從左上角開始往右下繪圖的關係,在這邊我們稱繪圖區為畫布,
畫布的區域為座標原點右下區域的為置,即第四象限。
有了新寬(w')高(h')了以後,我們還需要知道圖片被旋轉了以後,要如何平移才能將旋轉後的圖片放置到畫布中間。
旋轉且平移到畫布中的圖片應如下圖所示:
為了計算方便,我們採取以圖片中心點為旋轉中心的方式來推導,如下圖所示,
δh即δw為圖片以中心點旋轉後要平移的距離,可以看到推導後的結果為
δh = h'/2 - h/2
δw = w'/2 - w/2
經過了上述的推導後,我們得到了以下公式:
w'= h |sin(θ)| + w |cos(θ)|
h' = h |cos(θ)| + w |sin(θ)|
δh = h'/2 - h/2
δw = w'/2 - w/2
於是我們知道了旋轉圖片的程式邏輯:
1. 計算出新圖片的寬(w')高(h')
2.將圖片以中心點(w/2, h/2)為旋轉中心旋轉 θ
3.將圖片進行δw, δh 的平移使其位於第四象限的畫布中
最後,依照上述邏輯寫成的Java程式碼如下:
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class ImageRotateTest {
public static void main(String[] args) throws IOException {
String inputFilePath = "D:\\inputImage.png";
String outputFilePath = "D:\\outputRotatedImage.png";
File inputImage = new File(inputFilePath);
File outputImage = new File(outputFilePath);
BufferedImage newBufferedImage = rotateImage(ImageIO.read(inputImage), 60);
String outputImageExtension = outputFilePath.substring(outputFilePath.lastIndexOf(".") + 1);
ImageIO.write(newBufferedImage, outputImageExtension, outputImage);
}
public static BufferedImage rotateImage(BufferedImage bufferedimage, int degree) {
int w = bufferedimage.getWidth();
int h = bufferedimage.getHeight();
int type = bufferedimage.getColorModel().getTransparency();
double radiusDegree = Math.toRadians(degree);
double newH = Math.abs(h * Math.cos(radiusDegree)) + Math.abs(w * Math.sin(radiusDegree));
double newW = Math.abs(h * Math.sin(radiusDegree)) + Math.abs(w * Math.cos(radiusDegree));
double deltaX = (newW - w) / 2;
double deltaY = (newH - h) / 2;
BufferedImage img = new BufferedImage((int) newW, (int) newH, type);
Graphics2D graphics2d = img.createGraphics();
graphics2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
// 以下為矩陣相乘, [translate] * [rotate] * [image的x,y]
// 所以行為為,先rotate,再translate
graphics2d.translate(deltaX, deltaY);
graphics2d.rotate(radiusDegree, w / 2, h / 2); //以中心點旋轉
graphics2d.drawImage(bufferedimage, 0, 0, null);
graphics2d.dispose();
return img;
}
}
先寫 Graphics2D.translate() 再 Graphics2D.rotate()
原始碼下載:
ImageRotateTest.7z
旋轉圖片推導
參考資料:
P.S.
改使用ImageIcon的程式 (速度比較快)
ImageIcon imageIcon = new ImageIcon(fromSrc);
File toImage = new File(toSrc);
int w= imageIcon.getIconWidth();
int h= imageIcon.getIconHeight();
int imageType= BufferedImage.TYPE_INT_RGB;
double radiusDegree = Math.toRadians(degree);
//calculate new width/height of rotated image
double newW = Math.abs(h * Math.sin(radiusDegree)) + Math.abs(w * Math.cos(radiusDegree));
double newH = Math.abs(h * Math.cos(radiusDegree)) + Math.abs(w * Math.sin(radiusDegree));
//calculate new width/height offset of rotated image
double deltaX = (newW - w) / 2;
double deltaY = (newH - h) / 2;
BufferedImage img = new BufferedImage((int) newW, (int) newH, imageType);
Graphics2D graphics2d= img.createGraphics();
graphics2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
//Image Transoform Matrix : �A [translate] * [rotate] * [x,y of Image]
//rotate --> translate
graphics2d.translate(deltaX, deltaY);
graphics2d.rotate(radiusDegree, w/2, h/2);
graphics2d.drawImage(imageIcon.getImage(), 0, 0, null);
graphics2d.dispose();
String outputfileExtension = toSrc.substring(toSrc.lastIndexOf(".") + 1);
ImageIO.write(img, outputfileExtension, toImage);







作者已經移除這則留言。
回覆刪除感謝你的分享
刪除如推導的公式中,在有對sin(θ)及cos(θ)做絕對值取值的情況下,
其實是不需要計算新的角度的,即 :
w'= h |sin(θ)| + w |cos(θ)|
h' = h |cos(θ)| + w |sin(θ)|
你的方式也是一種方法,
因為使用了你的方法計算出來的新角度,假設為θ',用sin, cos去取值都是正值的,即 :
sin(θ') = |sin(θ)|
cos(θ') = |cos(θ)|
所以跟我的方法算是等價的
抱歉阿~我沒看推導看程式,又眼殘看成相加起來再取絕對值XD,想說怪怪的! 獻醜了
刪除