2012-09-18

如何計算及取得縮圖的正確尺寸

不管是網頁或是桌面應用程式,常常會需要縮圖處理。目前個人開發的照片小工具也需要縮圖顯示,把原始圖片縮到指定的寬高,並且符合比例,聽起來很簡單,不過也是要花一些時間思考演算法。

網路上可查到的,通常都是判斷寬是否大於高、寬等於高、寬小於高,然後分別作處理。這樣的寫法,在指定縮圖尺寸是固定的時候可用,但有時候縮圖寬高是會變動的,原本縮圖區是橫向,但也有可能會變成直向,這時候寫死的做法就無用了。請看底下圖例:


紅框是顯示縮圖區,藍色線表示可左右上下調整大小。原本左邊的縮圖區是橫向,原始照片是直向,所以在縮圖區左右留白;但是拉動大小之後,縮圖區變成直向,而且比例過長,原始照片依照比例縮圖的話,應該是要在縮圖區上下留白才是正確顯示。

然而圖片不是只有橫向或直向的長方形而已,還有正方形,然而光一個橫向的縮圖就會有很多判斷了…

請看底下範例:

黑色框是橫向縮圖區,紅色和綠色框都是橫向照片,但因為比例不同,紅色圖是把寬設成與縮圖相同,高照比例縮;綠色圖是把高設成與縮圖相同,寬照比例縮。黑色區域大小會變動(有可能變橫向或直向),你可以想想這樣的判斷要怎麼寫?

所以如果用上面說的,單純判斷縮圖和原始圖寬是否大於高的方式,裡面的if判斷會寫到死。

既然如此,最好就是計算縮圖區與原始照片的寬高比例(寬/高),如果原始照片的比值大於縮圖區的比值,就把原始照片的寬指定成縮圖區的寬,高依比例縮;如果原始照片比值小於縮圖區比值,則把原始照片的高指定成縮圖區的高,寬依比例縮。最後如果比例相同,那就寬高都設成跟縮圖區一樣就好了,這樣完全不用判斷圖片的方向啦~

底下是c#的程式碼範例,作用是傳回一個指定寬高的Image類別縮圖
/// <summary>
/// 取得縮圖區正確的顯示尺寸
/// </summary>
/// <param name="SourceImg">原始圖</param>
/// <param name="ThumbWidth">要縮成的寬度</param>
/// <param name="ThumbHeight">要縮成的高度</param>
/// <returns></returns>
public Image GetThumb(Bitmap SourceImg, int ThumbWidth, int ThumbHeight) {
    Image TargetImg = new Bitmap(ThumbWidth, ThumbHeight);
    Image tmp = null;
    Image.GetThumbnailImageAbort myCallback = new Image.GetThumbnailImageAbort(ThumbnailCallback);
    int NewWidth = 0;
    int NewHeight = 0;
    int XOffset = 0;
    int YOffset = 0;

    //比例會有小數,如果用int會無法真正判斷是否相等,造成縮圖錯亂
    float ThumbScale = (float)ThumbWidth / (float)ThumbHeight;
    float SourceScale = (float)SourceImg.Width / (float)SourceImg.Height;

    if (SourceScale == ThumbScale) {
        //圖片的長寬比例與Box相同,w h直接給縮圖的尺寸即可
        NewWidth = ThumbWidth;
        NewHeight = ThumbHeight;
    }
    if (SourceScale > ThumbScale) { //圖片寬高比大於縮圖寬高比,將w指定為ThumbWidth
        NewWidth = ThumbWidth;

        //a:b=c:x,x = b*c/a,國小數學
        NewHeight = (int)(SourceImg.Height * NewWidth / SourceImg.Width);
    }
    if (SourceScale < ThumbScale) { //圖片寬高比小於縮圖寬高比,將h指定為ThumbHeight
        NewHeight = ThumbHeight;
        NewWidth = (int)(SourceImg.Width * NewHeight / SourceImg.Height);
    }

    tmp = SourceImg.GetThumbnailImage(NewWidth, NewHeight, myCallback, IntPtr.Zero);
    XOffset = (int)((ThumbWidth - NewWidth) / 2);
    YOffset = (int)((ThumbHeight - NewHeight) / 2);

    using (Graphics g = Graphics.FromImage(TargetImg)) {
        g.DrawImage(tmp, XOffset, YOffset, tmp.Width, tmp.Height);
        g.DrawRectangle(Pens.Transparent, 0, 0, TargetImg.Width - 1, TargetImg.Height - 1);
    }

    return TargetImg;
}
private bool ThumbnailCallback() {
    return true;
}