新しく書き直した
↑の記事のほうがいい
ここから↓はいまいち
WPF、画像をくっきり表示させたい(ぼやけるのがイヤな)とき、EdgeModeとScalingMode ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/14912530.html
このときの方法が一番いいけど今回は画像のピクセルごとの色を変換して試してみた

これを最近傍補間法で縦横5倍に拡大
部分的に抜き出して等倍表示すると


拡大時にドットがはっきりするのが最近傍補間法の特徴
縮小ならどうなのか


0.3倍なら?

葉脈も違和感あるけど、そんなに悪くないねえ
図形画像の場合

白黒の1ピクセルごとの縦縞

2.1倍

2.2倍

2.5倍

0.5倍

試してわかったのが自分で書いた方法は処理が遅い
WPFのRenderTransformを使ったほうが簡単だし処理も格段に速い、10倍以上速い、もしかしたら100倍くらい違うかもってくらい速い
using System; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; namespace _20180416_拡大縮小_最近傍補間法 { public partial class MainWindow : Window { BitmapSource OriginBitmap; string ImageFileFullPath; public MainWindow() { InitializeComponent(); Title = this.ToString(); AllowDrop = true; Drop += MainWindow_Drop; SliderXScale.MouseWheel += SliderScale_MouseWheel; SliderYScale.MouseWheel += SliderScale_MouseWheel; MyButtonSave.Click += MyButtonSave_Click; MyButtonOrigin.Click += MyButtonOrigin_Click; MyButton2x2.Click += MyButton2x2_Click; MyButton1.Click += MyButton1_Click; MyButton2.Click += MyButton2_Click; } private void MyButtonSave_Click(object sender, RoutedEventArgs e) { if (OriginBitmap == null) { return; } SaveImage((BitmapSource)MyImage.Source); } private void MyButtonOrigin_Click(object sender, RoutedEventArgs e) { if (OriginBitmap == null) { return; } MyImage.Source = OriginBitmap; } private void MyButton2x2_Click(object sender, RoutedEventArgs e) { if (OriginBitmap == null) { return; } MyImage.Source = F4今表示している画像をニアレストネイバー法で2倍((BitmapSource)MyImage.Source); } private void SliderScale_MouseWheel(object sender, MouseWheelEventArgs e) { Slider s = (Slider)sender; if (e.Delta > 0) { s.Value += 0.1; } else { s.Value -= 0.1; } } private void MyButton2_Click(object sender, RoutedEventArgs e) { if (OriginBitmap == null) { return; } MyImage.Source = F2最近傍補間法カラー(OriginBitmap, SliderXScale.Value, SliderYScale.Value); } private void MyButton1_Click(object sender, RoutedEventArgs e) { if (OriginBitmap == null) { return; } var b = new FormatConvertedBitmap(OriginBitmap, PixelFormats.Gray8, null, 0); MyImage.Source = F1最近傍補間法グレースケール(b, SliderXScale.Value, SliderYScale.Value); } //今表示している画像をニアレストネイバー法で2倍表示 private BitmapSource F4今表示している画像をニアレストネイバー法で2倍(BitmapSource source) { if (source.PixelHeight > 1000) { if (MessageBox.Show( "縦ピクセル2000以上の大きな画像になる、実行する?", "処理実行確認", MessageBoxButton.OKCancel) == MessageBoxResult.Cancel) { return source; } } var wb = new WriteableBitmap(source); int h = wb.PixelHeight; int w = wb.PixelWidth; int stride = wb.BackBufferStride; var pixels = new byte[h * stride]; wb.CopyPixels(pixels, stride, 0); int nH = h * 2; int nW = w * 2; var nWb = new WriteableBitmap(nW, nH, 96, 96, source.Format, source.Palette); int nStride = nWb.BackBufferStride; var nPixels = new byte[nH * nStride]; long nP = 0, p = 0; int cc = source.Format.BitsPerPixel / 8;//8bitグレースケールなら1、32bppなら4 for (int y = 0; y < nH; ++y) { for (int x = 0; x < nW; ++x) { nP = y * nStride + (x * cc); p = (y / 2) * stride + ((x / 2) * cc); for (int i = 0; i < cc; ++i) { nPixels[nP + i] = pixels[p + i]; } } } nWb.WritePixels(new Int32Rect(0, 0, nW, nH), nPixels, nStride, 0); return nWb; } /// <summary> /// BitmapSourceを最近傍補間法で拡大縮小 /// </summary> /// <param name="source">pixelsFormat.Pbgra32専用</param> /// <param name="xScale">横倍率</param> /// <param name="yScale">縦倍率</param> /// <returns></returns> private BitmapSource F2最近傍補間法カラー(BitmapSource source, double xScale, double yScale) { var wb = new WriteableBitmap(source); int h = wb.PixelHeight; int w = wb.PixelWidth; int stride = wb.BackBufferStride; var pixels = new byte[h * stride]; wb.CopyPixels(pixels, stride, 0); long p = 0; //変換後のサイズは四捨五入 int nH = (int)(Math.Round((h * yScale), MidpointRounding.AwayFromZero));//普通のintキャストだと10*1.2で11.999は11になる int nW = (int)(Math.Round((w * xScale), MidpointRounding.AwayFromZero)); var nWb = new WriteableBitmap(nW, nH, 96, 96, source.Format, source.Palette); int nStride = nWb.BackBufferStride; var nPixels = new byte[nH * nStride]; long nP = 0; int motoX, motoY; for (int y = 0; y < nH; ++y) { for (int x = 0; x < nW; ++x) { motoX = (int)(x * (w / (float)nW));//途中は小数点ありの計算で、最後にintキャストで小数点以下切り捨て motoY = (int)(y * (h / (float)nH)); p = motoY * stride + (motoX * 4);//元画像での位置 nP = y * nStride + (x * 4);//変換後の位置 //変換後の色指定 nPixels[nP + 3] = pixels[p + 3];//アルファ nPixels[nP + 2] = pixels[p + 2];//赤 nPixels[nP + 1] = pixels[p + 1];//緑 nPixels[nP] = pixels[p];//青 } } nWb.WritePixels(new Int32Rect(0, 0, nW, nH), nPixels, nStride, 0); return nWb; } //グレースケール限定 private BitmapSource F1最近傍補間法グレースケール(BitmapSource source, double xScale, double yScale) { var wb = new WriteableBitmap(source); int h = wb.PixelHeight; int w = wb.PixelWidth; int stride = wb.BackBufferStride; var pixels = new byte[h * stride]; wb.CopyPixels(pixels, stride, 0); long p = 0; //変換後のサイズは四捨五入 int nH = (int)(Math.Round((h * yScale), MidpointRounding.AwayFromZero));//普通のintキャストだと10*1.2で11.999になる int nW = (int)(Math.Round((w * xScale), MidpointRounding.AwayFromZero)); var nWb = new WriteableBitmap(nW, nH, 96, 96, source.Format, source.Palette); int nStride = nWb.BackBufferStride; var nPixels = new byte[nH * nStride]; long nP = 0; double xx, yy; int motoX, motoY; for (int y = 0; y < nH; ++y) { for (int x = 0; x < nW; ++x) { //今/(変換後/元)、これならずれない //xx = x / (nW / (float)w); //yy = y / (nH / (float)h); //今/(変換後/元)、これならずれない xx = x * (w / (float)nW); yy = y * (h / (float)nH); //切り捨て motoX = (int)xx; motoY = (int)yy; p = motoY * stride + motoX; //四捨五入はズレる //motoX = (int)(Math.Round(xx, MidpointRounding.AwayFromZero)); //motoY = (int)(Math.Round(yy, MidpointRounding.AwayFromZero)); //p = motoY * stride + motoX; nP = y * nStride + (x * 1); nPixels[nP] = pixels[p]; } } nWb.WritePixels(new Int32Rect(0, 0, nW, nH), nPixels, nStride, 0); return nWb; } private void MainWindow_Drop(object sender, DragEventArgs e) { if (e.Data.GetDataPresent(DataFormats.FileDrop) == false) { return; } string[] filePath = (string[])e.Data.GetData(DataFormats.FileDrop); //OriginBitmap = GetBitmapSourceWithChangePixelFormat2(filePath[0], PixelFormats.Gray8, 96, 96); OriginBitmap = GetBitmapSourceWithChangePixelFormat2(filePath[0], PixelFormats.Pbgra32, 96, 96); if (OriginBitmap == null) { MessageBox.Show("not Image"); } else { MyImage.Source = OriginBitmap; ImageFileFullPath = filePath[0]; } } private void SaveImage(BitmapSource source) { var saveFileDialog = new Microsoft.Win32.SaveFileDialog(); saveFileDialog.Filter = "*.png|*.png|*.bmp|*.bmp|*.tiff|*.tiff|*.jpg|*.jpg"; saveFileDialog.AddExtension = true; saveFileDialog.FileName = System.IO.Path.GetFileNameWithoutExtension(ImageFileFullPath) + "_"; saveFileDialog.InitialDirectory = System.IO.Path.GetDirectoryName(ImageFileFullPath); if (saveFileDialog.ShowDialog() == true) { BitmapEncoder encoder = new BmpBitmapEncoder(); if (saveFileDialog.FilterIndex == 1) { encoder = new PngBitmapEncoder(); } else if (saveFileDialog.FilterIndex == 2) { encoder = new BmpBitmapEncoder(); } else if (saveFileDialog.FilterIndex == 3) { encoder = new TiffBitmapEncoder(); } else if (saveFileDialog.FilterIndex == 4) { var je = new JpegBitmapEncoder(); je.QualityLevel = 96; encoder = je; } encoder.Frames.Add(BitmapFrame.Create(source)); using (var fs = new System.IO.FileStream(saveFileDialog.FileName, System.IO.FileMode.Create, System.IO.FileAccess.Write)) { encoder.Save(fs); } } } /// <summary> /// ファイルパスとPixelFormatを指定してBitmapSourceを取得、dpiの変更は任意 /// </summary> /// <param name="filePath">画像ファイルのフルパス</param> /// <param name="pixelFormat">PixelFormatsの中からどれかを指定</param> /// <param name="dpiX">無指定なら画像ファイルで指定されているdpiになる</param> /// <param name="dpiY">無指定なら画像ファイルで指定されているdpiになる</param> /// <returns></returns> private BitmapSource GetBitmapSourceWithChangePixelFormat2( string filePath, PixelFormat pixelFormat, double dpiX = 0, double dpiY = 0) { BitmapSource source = null; try { using (System.IO.FileStream fs = new System.IO.FileStream(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read)) { var bf = BitmapFrame.Create(fs); var convertedBitmap = new FormatConvertedBitmap(bf, pixelFormat, null, 0); int w = convertedBitmap.PixelWidth; int h = convertedBitmap.PixelHeight; int stride = (w * pixelFormat.BitsPerPixel + 7) / 8; byte[] pixels = new byte[h * stride]; convertedBitmap.CopyPixels(pixels, stride, 0); //dpi指定がなければ元の画像と同じdpiにする if (dpiX == 0) { dpiX = bf.DpiX; } if (dpiY == 0) { dpiY = bf.DpiY; } //dpiを指定してBitmapSource作成 source = BitmapSource.Create( w, h, dpiX, dpiY, convertedBitmap.Format, convertedBitmap.Palette, pixels, stride); }; } catch (Exception) { } return source; } } }
デザイン画面

コード
アプリ
参照したところ
画像処理 - HexeRein
http://www7a.biglobe.ne.jp/~fairytale/article/program/graphics.html#nearest_neighbour
【画像処理】最近傍補間法の原理・計算式 | アルゴリズム雑記
https://algorithm.joho.info/image-processing/nearest-neighbor-linear-interpolation/
画素の補間(Nearest neighbor,Bilinear,Bicubic) 画像処理ソリューション
http://imagingsolution.blog107.fc2.com/blog-entry-142.html
関連記事
次回、2018/04/17は1日後
バイリニア法で画像の拡大縮小 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15464617.html
2019/11/08は1年7ヶ月後
これでようやく納得行くのができた
