2010年6月11日

イメージの拡大と縮小処理

なにやらJPEG画像ファイルを縮小しなくてはいけない処理があるらしい。
拡大はないんですね、縮小オンリーなんですか?
→いえ、稀に拡大もありますよby客

こういう「稀に」っていうのは大抵頻繁にあるものです。お客さんの言葉は信用しちゃいけません。
妙に歪んだ縦横比の画像用意されたり、半月も経つと調子に乗ってバカバカ使うようになったりね。

でもまあ、拡大の方は楽です。説明が。
小さい画像でかくしたんだからそりゃ画質悪くなりますって!と
でも縮小は納得してくれない人多数発生(特にうちの上層部)。o><)o
小さくするだけなのになんでこんなギザギザした画像になるんだ!
とか
縮小なのになんでこんなに処理時間がかかるんだ!
とかね。

ぶっちゃけて言うと拡大より縮小の方がめんどいです。
エッジ強調とか粒子状のノイズとかも出ますから。
数式紙に書き出して叩きつけようと思ったり思わなかったりラジバンダ(ry

ってわけで
2パターンの縮小ロジックを作らされる羽目に(≡д≡)
速度と画質好きな方選んでください!と逃げたというのが本音♪
ま、こんなロジックになりました。




まずはFramework標準で一番きれいになるとうわさされる双三次補間法
System.DrawingのInterpolationMode.HighQualityBicubicですね。
もうひとつはLanczos(ランチョシュ?ランツォシュ?)による計算です。

Lanczosの式は以下のサイトを使わせてもらいました。感謝!
http://www7a.biglobe.ne.jp/~fairytale/article/program/graphics.html

と。まあここまでであればこんな風に覚え書きは書きません。
比べ物にならないほど分かりやすいサイトはいっぱいありますから。


追加のご要望はこちら♪
「画像は決まったサイズにしてほしいんですけど♪」

500×500とか320×320で必ず出力してください。ってことのようです。
厄介なご要望で・・・と言えない下っ端PGなのでコツコツと修正しました。
で。こんな感じです。


C#標準で縮小してみた。
※画像変換の作業領域として見えないPictureBox使ってます。
多分使わなくてもいい方法もありそうですが、終電近づき時間もなかったので・・・


  1. Bitmap src = new Bitmap(元画像);
  2. Graphics g = PictureBox作業領域.CreateGraphics();
  3.  
  4. int w = 500; //作りたい画像の縦横ですね
  5. int h = 500;
  6. = src.Width * 500 / src.Height;
  7. if (> 500)
  8. {
  9.  w = 500;
  10.  h = src.Height * 500 / src.Width; //長いほうのサイズに合わせてます
  11. }
  12. Bitmap dest = new Bitmap(500500);
  13. = Graphics.FromImage(dest);
  14. g.Clear(Color.White);
  15. g.InterpolationMode = InterpolationMode.HighQualityBicubic;
  16. g.DrawImage(src, 00, w, h);
  17. PictureBox作業領域.SizeMode = PictureBoxSizeMode.Zoom;
  18. PictureBox作業領域.Image  = dest;
  19. PictureBox作業領域.Image.Save(画像の保存先, 拡張子情報);

Jpegで出力すればいいってことなので透過色のことは考えてないです。
上のコードならdestのMakeTransparent設定すればPNGの透過色も出来ると思いますが未確認。

今回は開いた空間の背景は白にしてますが
g.Clear(Color.White);
を変更すれば他の色でも出来るでしょう。


Lanczosのロジックは基本参考にしたサイトのままです。
変更したのは計算後のピクセルをセットするBitmapオブジェクトを500×500にして
背景を白に、そして縦横長い方を基本とするってとこくらいでしょうか?


  1. //画像は500×500に
  2. image2 = new Bitmap(500500);
  3.  
  4. resize = 0d;
  5. if (image.Width > image.Height)
  6. {
  7.  resize = 500d / image.Width;
  8. }
  9. else
  10. {
  11.  resize = 500d / image.Height;
  12. }

※doubleで比率計算するので最後に「d」付けないと飛びます

あとはこのresize変数をLanczos計算範囲のBaseに適用してあげればOKです。
背景もColorの値をWhiteにすれば変わりますからね。


あ、拡張子の情報は事前にImageFormatで元画像から判定してます。
出力はJpeg固定の仕様で受けたのであまり意味のないロジックですが・・・
↓こんな感じです。


  1. ImageFormat imgF = null;
  2. System.IO.Stream sr = null;
  3. System.IO.FileStream fs = null;
  4. fs = new FileStream(元画像, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
  5. sr = fs;
  6. // イメージ判定
  7. Bitmap bitmap  = new Bitmap(sr);
  8. ImageCodecInfo[] decoders = ImageCodecInfo.GetImageDecoders();
  9.  
  10. foreach (ImageCodecInfo ici in decoders)
  11. {
  12.  if (ici.FormatID == bitmap.RawFormat.Guid)
  13.  {
  14.   switch (ici.FormatDescription)
  15.   {
  16.    case "BMP":
  17.     imgF = ImageFormat.Bmp;
  18.     break;
  19.    case "JPEG":
  20.     imgF = ImageFormat.Jpeg;
  21.     break;
  22.    case "GIF":
  23.     imgF = ImageFormat.Gif;
  24.     break;
  25.    case "EMF":
  26.     imgF = ImageFormat.Emf;
  27.     break;
  28.    case "WMF":
  29.     imgF = ImageFormat.Wmf;
  30.     break;
  31.    case "TIFF":
  32.     imgF = ImageFormat.Tiff;
  33.     break;
  34.    case "PNG":
  35.     imgF = ImageFormat.Png;
  36.     break;
  37.    case "ICO":
  38.     imgF = ImageFormat.Icon;
  39.     break;
  40.    default:
  41.     imgF = ImageFormat.Bmp;
  42.     break;
  43.   }
  44.   break;
  45.  }
  46. }
  47. fs.Close();
  48. fs.Dispose();
  49. sr.Close();
  50. sr.Dispose();
  51. bitmap.Dispose();

いやー継ぎ接ぎだらけのプログラムなこって(≡^∇^≡)ニャハハ