バイリニア法(bilinear interpolation)を
WPFと
C#で書いてみた
だいたいあっていると思う
2021/04/15追記
間違っていたので書き直した(再挑戦した)のが
これはかなり正解に近いと思う(フラグ)
2021/04/15追記ここまで
距離に応じて補間する
左右の中間なので色も中間で150
求め方は
a:(200+100)/2=150
b:200*0.5+100*0.5=150
距離に応じて補間するからbの方法
次
元の全体の距離(大きさ)は1-0=1
拡大後の全体の距離は3-0=3
元/拡大後=1/3=0.33333…これが拡大後から見た倍率になるので
拡大後座標1に0.3333をかけて1*(0.33)=0.33が元の位置になる
0から0.33離れたところ、1から0.67離れたところになる
距離が近いほど影響を受けるので
0からは1-0.33=0.67、1からは1-0.67=0.33の影響を受けるので
それぞれの値は
200*(1-0.33)=134
100*(1-0.67)=33
これを合計して134+33=167が拡大後座標1の値になる
同じように拡大後座標2は
2*0.33=0.66が元の位置で
200*(1-0.66)+100*0.66=134
別の場合でサイズが25x30の画像を横3.2倍、縦1.5倍にしたとき
25*3.2=80
30*1.5=45
サイズは80x45になって
拡大後から見た距離の倍率は
(25-1)/(80-1)≒0.3
(30-1)/(45-1)≒0.66
このときの拡大後(14,26)の座標は元で言うと(4.2,17.16)
14*0.3=4.2
26*0.66=17.16
●が(4.2,17.16)
4.5は4と5の間、17.16は17と18の間なので参照する元の座標は
(4,17)(5,17)(4,18)(5,18)の四点●になる
それぞれの値が34,196,187,4だった場合は
yの小数点部分は無視してy=17とy=18のときのx=4.2を考える
x=4.2は4から0.2の距離、5から0.8の距離
y=17でx=4.2
34*0.8 + 196*0.2=66.4
y=18でx=4.2
187*0.8 + 4*0.2=150.4
次にy=17.16、これは17から0.16の距離、18からは0.84の距離になるので
17の値は66.4、18の値は150.4から距離に応じた値を取って
66.4*0.84 + 150.4*0.16=79.84
コード
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace _20180417_拡大縮小_バイリニア法
{
public partial class MainWindow : Window
{
BitmapSource OriginBitmap;
string ImageFileFullPath;
public MainWindow()
{
InitializeComponent();
Title = this.ToString();
AllowDrop = true;
Drop += MainWindow_Drop;
MyButtonOrigin.Click += MyButtonOrigin_Click;
MyButtonSave.Click += MyButtonSave_Click;
MyButton1Bilinear2.Click += MyButton1_Click;
MyButtonBilinear2Color.Click += MyButton2_Click;
SliderXScale.MouseWheel += SliderRatio_MouseWheel;
SliderYScale.MouseWheel += SliderRatio_MouseWheel;
MyButton2x2.Click += MyButton2x2_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 SliderRatio_MouseWheel(object sender, MouseWheelEventArgs e)
{
Slider s = (Slider)sender;
if (e.Delta > 0) { s.Value += s.SmallChange; }
else { s.Value -= s.SmallChange; }
}
private void MyButton2_Click(object sender, RoutedEventArgs e)
{
if (OriginBitmap == null) { return; }
MyImage.Source = F2バイリニア法カラー2(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);
}
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;
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;
}
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);
int nH = (int)Math.Round(h * yScale, MidpointRounding.AwayFromZero);
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 motoX, motoY;
double rXScale = (w - 1) / (nW - 1.0f);
double rYScale = (h - 1) / (nH - 1.0f);
int x0, x1, y0, y1;
double xx, yy;
byte topLeft, topRight, botLeft, botRight;
double topX, botX, newValue;
for (int y = 0; y < nH; ++y)
{
motoY = y * rYScale;
yy = motoY % 1;
y0 = (int)(motoY - yy);
y1 = y0 != h - 1 ? y0 + 1 : y0;
for (int x = 0; x < nW; ++x)
{
motoX = x * rXScale;
xx = motoX % 1;
x0 = (int)(motoX - xx);
x1 = x0 != w - 1 ? x0 + 1 : x0;
topLeft = pixels[y0 * stride + (x0)];
topRight = pixels[y0 * stride + (x1)];
botLeft = pixels[y1 * stride + (x0)];
botRight = pixels[y1 * stride + (x1)];
topX = topLeft * (1 - xx) + topRight * xx;
botX = botLeft * (1 - xx) + botRight * xx;
newValue = topX * (1 - yy) + botX * yy;
newValue = Math.Round(newValue, MidpointRounding.AwayFromZero);
nP = y * nStride + (x * 1);
nPixels[nP] = (byte)newValue;
}
}
nWb.WritePixels(new Int32Rect(0, 0, nW, nH), nPixels, nStride, 0);
return nWb;
}
private BitmapSource F2バイリニア法カラー2(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);
int nH = (int)Math.Round(h * yScale, MidpointRounding.AwayFromZero);
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 motoX, motoY;
double rXScale = (w - 1) / (nW - 1.0f);
double rYScale = (h - 1) / (nH - 1.0f);
int x0, x1, y0, y1;
double xx, yy;
byte topLeft, topRight, botLeft, botRight;
double topX, botX, newValue;
for (int y = 0; y < nH; ++y)
{
motoY = y * rYScale;
yy = motoY % 1;
y0 = (int)(motoY - yy);
y1 = y0 != h - 1 ? y0 + 1 : y0;
for (int x = 0; x < nW; ++x)
{
motoX = x * rXScale;
xx = motoX % 1;
x0 = (int)(motoX - xx);
x1 = x0 != w - 1 ? x0 + 1 : x0;
for (int i = 0; i < 4; ++i)
{
topLeft = pixels[y0 * stride + (x0 * 4) + i];
topRight = pixels[y0 * stride + (x1 * 4) + i];
botLeft = pixels[y1 * stride + (x0 * 4) + i];
botRight = pixels[y1 * stride + (x1 * 4) + i];
topX = topLeft * (1 - xx) + topRight * xx;
botX = botLeft * (1 - xx) + botRight * xx;
newValue = topX * (1 - yy) + botX * yy;
newValue = Math.Round(newValue, MidpointRounding.AwayFromZero);
nP = y * nStride + (x * 4 + i);
nPixels[nP] = (byte)newValue;
}
}
}
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.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>
</summary>
<param name="filePath"></param>
<param name="pixelFormat"></param>
<param name="dpiX"></param>
<param name="dpiY"></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);
if (dpiX == 0) { dpiX = bf.DpiX; }
if (dpiY == 0) { dpiY = bf.DpiY; }
source = BitmapSource.Create(
w, h, dpiX, dpiY,
convertedBitmap.Format,
convertedBitmap.Palette, pixels, stride);
};
}
catch (Exception)
{
}
return source;
}
}
}
デザイン画面
昨日のニアレストネイバーと比較
↓ニアレストネイバー
縮小
↑バイリニア
↓ニアレストネイバー
ほとんど変わらないなあ、もっと差が出ると思っていた
2018/04/27追記
縮小処理で差が出ないのは縮小処理が間違っていたからだったよ…
正確には縮小処理は拡大処理とは別なのに拡大と同じ方法で行っていたから
図形
0.5倍に縮小
グラデーションになった
これは違うかなあ、おかしいなあ
0.6倍
これなら納得できる
□■
■□
白と黒が互いに並んでいる
これを10倍
拡大して確認
よくわからんw
昨日のニアレストネイバーもそうだけど
WPFならRenderTransformを使ったほうが速い
参照したところ
コード
アプリ
関連記事
2018/04/20、次
2018/04/16、前
2017/5/13