昨天又使用了某個多年以前寫的,或者說是“收集”而來的方法。這個方法的作用是根據一幅圖片(一般是幅大圖)生成它的縮略圖。這個方法用了許多年了,一直沒有去懷疑過它的正確性,但是昨天忽然發現它一直以來都存在一個問題,雖然可能不是那麼明顯,而且也不會造成太大問題(否則早就發現了)——但是,這的確是個不妥的地方。這個問題在我看來也有一定借鑒意義,因此我打算把它展示出來。那麼,您能否看出它究竟是錯在什麼地方了呢?
生成縮略圖的規則很簡單,概括地說有三點:
包含圖片完整內容,以及長寬比不變。
尺寸盡可能大,但如果圖片本身很小,也不做拉伸。
不超過指定的width * height的范圍內。
這個規則其實就是最傳統的縮略圖生成方式,使用如Windows照片浏覽器等軟件打開圖片後,一般來說默認都會如此調整圖片尺寸。而我們如果需要寫一段代碼來實現這一點也並不困難,以下便是我用了許多年的方法:
/// <summary>
/// Creates a thumbnail from an existing image. Sets the biggest dimension of the
/// thumbnail to either desiredWidth or Height and scales the other dimension down
/// to preserve the ASPect ratio
/// </summary>
/// <param name="imageStream">stream to create thumbnail for</param>
/// <param name="desiredWidth">maximum desired width of thumbnail</param>
/// <param name="desiredHeight">maximum desired height of thumbnail</param>
/// <returns>Bitmap thumbnail</returns>
public Bitmap CreateThumbnail(Bitmap originalBmp, int desiredWidth, int desiredHeight)
{
// If the image is smaller than a thumbnail just return it
if (originalBmp.Width <= desiredWidth && originalBmp.Height <= desiredHeight)
{
return originalBmp;
}
int newWidth, newHeight;
// scale down the smaller dimension
if ((decimal)desiredWidth / originalBmp.Width < (decimal)desiredHeight / originalBmp.Height)
{
decimal desiredRatio = (decimal)desiredWidth / originalBmp.Width;
newWidth = desiredWidth;
newHeight = (int)(originalBmp.Height * desiredRatio);
}
else
{
decimal desiredRatio = (decimal)desiredHeight / originalBmp.Height;
newHeight = desiredHeight;
newWidth = (int)(originalBmp.Width * desiredRatio);
}
// This code creates cleaner (though bigger) thumbnails and properly
// and handles GIF files better by generating a white background for
// transparent images (as opposed to black)
// This is preferred to calling Bitmap.GetThumbnailImage()
Bitmap bmpOut = new Bitmap(newWidth, newHeight);
using (Graphics graphics = Graphics.FromImage(bmpOut))
{
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.FillRectangle(Brushes.White, 0, 0, newWidth, newHeight);
graphics.DrawImage(originalBmp, 0, 0, newWidth, newHeight);
}
return bmpOut;
}
它的具體來源我已經記不得了,不過從英文注釋上來看這應該是個老外寫的代碼,那麼我們現在就來解釋一番。首先,這個方法會先判斷源圖片的大小是否已經可以放入目標區域(desiredWidth * desiredHeight)中了,如果是,則直接返回源圖片。如果不滿足第一個判斷,則說明寬和高之中至少有一個超出了目標尺寸,而我們要對源圖片進行等比例縮放。
那麼縮放的“比例”又是多少呢?自然是“寬”或“高”中縮放“程度大”的那個。因為如果按照縮放程度小的那條邊的比例來改變圖片尺寸,那麼另一條邊勢必會超出范圍。因此,我們接下來便是比較desiredWidth與originalBmp.Width之比,以及desiredHeight與 originalBmp.Height之比孰大孰小。哪個小,則意味著我們要把它作為縮放依據,因為它對圖片尺寸的限制要比另一條邊來的嚴格。於是乎,再第二個條件判斷的任意一個分支中,我們都可以計
算出縮放的比例(desiredRatio),然後把作為“依據”的那條邊設為 desiredWidth/Height,將另一條邊根據縮放比例進行調整。在計算和比較過程中我們都使用了decimal數據類型,因為它是.Net中精度最高的浮點數類型,我們以此減少計算過程中所帶來的誤差。
至於得到了newWidth和newHeight之後,我們便只要根據這個尺寸生成目標圖片即可,它便是源圖片的縮略圖,符合我們之前提出的三個要求。
聽起來很簡單,看上去也沒有什麼問題,不是嗎?不過,其實這個實現中有一個不那麼明顯的問題,您發現了嗎