画像のサイズ変更-品質が非常に悪い場合がありますか?
-
03-07-2019 - |
質問
一部の画像のサイズをユーザーの画面解像度に合わせて変更しています。アスペクト比が間違っている場合、画像をカットする必要があります。 私のコードは次のようになります:
protected void ConvertToBitmap(string filename)
{
var origImg = System.Drawing.Image.FromFile(filename);
var widthDivisor = (double)origImg.Width / (double)System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width;
var heightDivisor = (double)origImg.Height / (double)System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height;
int newWidth, newHeight;
if (widthDivisor < heightDivisor)
{
newWidth = (int)((double)origImg.Width / widthDivisor);
newHeight = (int)((double)origImg.Height / widthDivisor);
}
else
{
newWidth = (int)((double)origImg.Width / heightDivisor);
newHeight = (int)((double)origImg.Height / heightDivisor);
}
var newImg = origImg.GetThumbnailImage(newWidth, newHeight, null, IntPtr.Zero);
newImg.Save(this.GetBitmapPath(filename), System.Drawing.Imaging.ImageFormat.Bmp);
}
ほとんどの場合、これは正常に機能します。しかし、一部の画像では、結果の品質が非常に低い です。画像は非常に小さいサイズ(サムネイルサイズ)にサイズ変更され、再び拡大されたように見えます。しかし、画像の解像度は正確です。どうすればいいですか?
元の画像の例: 代替テキストhttp://img523.imageshack.us/img523/1430/naturaerowoods.jpg
注:私はWPFアプリケーションを持っていますが、サイズ変更にはWinForms関数を使用します。これは簡単で、トレイアイコンのSystem.Windows.Formsへの参照が既に必要であるためです。
解決
メソッドの最後の2行を次のように変更します。
var newImg = new Bitmap(newWidth, newHeight);
Graphics g = Graphics.FromImage(newImg);
g.DrawImage(origImg, new Rectangle(0,0,newWidth,newHeight));
newImg.Save(this.GetBitmapPath(filename), System.Drawing.Imaging.ImageFormat.Bmp);
g.Dispose();
他のヒント
現時点では.NETソースを覗くことはできませんが、おそらく問題は Image.GetThumbnailImage
メソッドにあります。 MSDNでさえ、「要求されたサムネイル画像のサイズが約120 x 120ピクセルの場合にうまく機能するが、サムネイルが埋め込まれた画像から大きなサムネイル画像(たとえば、300 x 300)を要求する」サムネイル画像の品質が著しく低下する可能性があります」。真のサイズ変更(サムネイルではない)を行うには、 Graphics.DrawImage
メソッドを使用する必要があります。また、必要に応じて品質を向上させるために、 Graphics.InterpolationMode
を使用する必要があります。
サムネイルを作成しない場合、 GetThumbnailImage
というメソッドを使用することは、おそらく良い考えではありません...
他のオプションについては、このCodeProjectの記事をご覧ください。 。特に、新しい画像を作成し、そのための Graphics
を作成し、補間モードを HighQualityBicubic
に設定して、元の画像をグラフィックスに描画します。少なくとも試してみる価値はあります。
MSDN に示されているように、 GetThumbnailImage()
は、任意の画像スケーリングを行うようには設計されていません。 120x120を超えるものはすべて手動でスケーリングする必要があります。代わりにこれを試してください:
using(var newImg = new Bitmap(origImg, newWidth, newHeight))
{
newImg.Save(this.GetBitmapPath(filename), System.Drawing.Imaging.ImageFormat.Bmp);
}
編集
明確化のポイントとして、この Bitmap
コンストラクターのオーバーロードは Graphics.DrawImage
を呼び出しますが、補間を制御することはできません。
このコードの代わりに:
newImg.Save(this.GetBitmapPath(filename), System.Drawing.Imaging.ImageFormat.Bmp);
これを使用:
System.Drawing.Imaging.ImageCodecInfo[] info = System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders();
System.Drawing.Imaging.EncoderParameters param = new System.Drawing.Imaging.EncoderParameters(1);
param.Param[0] = new System.Drawing.Imaging.EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
newImg.Save(dest_img, info[1], param);
たとえば、元の画像はJPGで、サイズ変更された画像はPNGです。意図的に形式を変換していますか?異なるロッシー圧縮スキームを切り替えると、品質が低下する可能性があります。
画像のサイズを変更するときに、画像のサイズを増減していますか?小さな画像から大きな画像を作成する場合、この種の劣化が予想されます。
画像を拡大すると間違いなく劣化します。
一部のカメラでは、おそらくデバイス自体でプレビューするために、サイズ変更されたサムネイルをファイル自体に配置します。
GetThumbnailメソッドは、高解像度メソッドを取得する代わりに、実際に画像ファイルに埋め込まれたこのサムネイル画像を取得します。
簡単な解決策は、サイズ変更やその他の操作を行う前に、.Netをtrickしてそのサムネイル情報を破棄させることです。そのように....
img.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipX);
//removes thumbnails from digital camera shots
img.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipX);
制約の大きさを変更しようとする場合、System.Drawing.Imageに拡張メソッドを作成しました。これは便利かもしれません。
/// <summary>
///
/// </summary>
/// <param name="img"></param>
/// <param name="size">Size of the constraining proportion</param>
/// <param name="constrainOnWidth"></param>
/// <returns></returns>
public static System.Drawing.Image ResizeConstrainProportions(this System.Drawing.Image img,
int size, bool constrainOnWidth, bool dontResizeIfSmaller)
{
if (dontResizeIfSmaller && (img.Width < size))
return img;
img.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipX);
img.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipX);
float ratio = 0;
ratio = (float)img.Width / (float)img.Height;
int height, width = 0;
if (constrainOnWidth)
{
height = (int)(size / ratio);
width = size;
}
else
{
width = (int)(size * ratio);
height = size;
}
return img.GetThumbnailImage(width, height, null, (new System.IntPtr(0)));
}
これは、次の要因に基づいて大きく異なります。
- 宛先解像度が「自然」にどれだけ一致するか元の解像度のスケール
- ソース画像の色深度
- 画像タイプ-一部は他よりも損失が大きい