2つの画像の各ピクセルの差を計算し表示しています。
実行環境構築
プロジェクトの作成
mkdir プロジェクト名
cd プロジェクト名
dotnet new winforms
code .
ソースプログラム
using System.Drawing.Imaging;
namespace ImgDiff;
public partial class Form1 : Form
{
// Bitmapオブジェクトをbyte配列に変換
static public byte[] BitmapToBytes(Bitmap src)
{
var bmpData = src.LockBits(
new Rectangle(0, 0, src.Width, src.Height),
ImageLockMode.ReadOnly,
src.PixelFormat);
byte[] bytes = new byte[Math.Abs(bmpData.Stride) * src.Height];
System.Runtime.InteropServices.Marshal.Copy(
bmpData.Scan0,
bytes, 0, bytes.Length);
src.UnlockBits(bmpData);
return bytes;
}
// byte配列をにBitmapオブジェクト変換
static public Bitmap BytesToBitmap(byte[] bytes, int width, int height, PixelFormat format)
{
var dst = new Bitmap(width, height, format);
var bmpData = dst.LockBits(
new Rectangle(0, 0, dst.Width, dst.Height),
ImageLockMode.ReadWrite,
dst.PixelFormat);
System.Runtime.InteropServices.Marshal.Copy(
bytes,
0, bmpData.Scan0,
bytes.Length);
dst.UnlockBits(bmpData);
return dst;
}
public Form1()
{
InitializeComponent();
Text = "画像の差分";
// ベース画像
var fnt = new Font("MS UI Gothic", 24);
var resultBitmap = new Bitmap(800, 600);
using (var g = Graphics.FromImage(resultBitmap))
{
g.FillRectangle(Brushes.White, 0, 0, resultBitmap.Width, resultBitmap.Height);
g.DrawString("結果", fnt, Brushes.Black, 0.0f, 0.0f);
}
var aBitmap = new Bitmap(800, 600);
using (var g = Graphics.FromImage(aBitmap))
{
g.FillRectangle(Brushes.White, 0, 0, aBitmap.Width, aBitmap.Height);
g.DrawString("A:画像をD&D", fnt, Brushes.Black, 0.0f, 0.0f);
}
var bBitmap = new Bitmap(800, 600);
using (var g = Graphics.FromImage(bBitmap))
{
g.FillRectangle(Brushes.White, 0, 0, bBitmap.Width, bBitmap.Height);
g.DrawString("B:画像をD&D", fnt, Brushes.Black, 0.0f, 0.0f);
}
// スプリットコンテナ
var scViewConsole = new SplitContainer
{
Dock = DockStyle.Fill,
Orientation = Orientation.Horizontal, // 上下
Panel1MinSize = 120,
};
// スプリットコンテナ
var scInOut = new SplitContainer
{
Dock = DockStyle.Fill,
Orientation = Orientation.Vertical, // 左右
Panel1MinSize = 240,
};
// スプリットコンテナ
var scAb = new SplitContainer
{
Dock = DockStyle.Fill,
Orientation = Orientation.Horizontal, // 上下
Panel1MinSize = 120,
};
// ピクチャボックス
var picboxResult = new PictureBox
{
SizeMode = PictureBoxSizeMode.AutoSize,
Image = (Bitmap)resultBitmap.Clone(),
};
// ピクチャボックス
var picboxA = new PictureBox
{
SizeMode = PictureBoxSizeMode.AutoSize,
Image = aBitmap,
};
// ピクチャボックス
var picboxB = new PictureBox
{
SizeMode = PictureBoxSizeMode.AutoSize,
Image = bBitmap,
};
// パネル
var flowPanel = new FlowLayoutPanel
{
Dock = DockStyle.Fill,
};
// 実行ボタン
var execButton = new Button
{
Text = "実行",
Size = new System.Drawing.Size(180, 50),
};
// コントロールの追加
flowPanel.Controls.AddRange(new Control[]
{
execButton,
});
scAb.Panel1.Controls.Add(picboxA);
scAb.Panel1.AutoScroll = true;
scAb.Panel1.AllowDrop = true;
scAb.Panel2.Controls.Add(picboxB);
scAb.Panel2.AutoScroll = true;
scAb.Panel2.AllowDrop = true;
scInOut.Panel1.Controls.Add(picboxResult);
scInOut.Panel1.AutoScroll = true;
scInOut.Panel1.AllowDrop = false;
scInOut.Panel2.Controls.Add(scAb);
scViewConsole.Panel1.Controls.Add(scInOut);
scViewConsole.Panel2.Controls.Add(flowPanel);
Controls.Add(scViewConsole);
// イベント
// ドラッグエンターイベント
scAb.Panel1.DragEnter += (sender, e) =>
{
if (e.Data == null) return;
if(!e.Data.GetDataPresent(DataFormats.FileDrop)) return;
e.Effect = DragDropEffects.Copy;
};
// ドラッグアンドドロップイベント
scAb.Panel1.DragDrop += (sender, e) =>
{
if (e.Data == null) return;
var fd = e.Data.GetData(DataFormats.FileDrop);
if (fd == null) return;
string? path = ((string[])fd)[0];
picboxA.Image?.Dispose();
using(var fs = new FileStream(path, FileMode.Open))
{
picboxA.Image = new Bitmap(fs);
}
};
// ドラッグエンターイベント
scAb.Panel2.DragEnter += (sender, e) =>
{
if (e.Data == null) return;
if(!e.Data.GetDataPresent(DataFormats.FileDrop)) return;
e.Effect = DragDropEffects.Copy;
};
// ドラッグアンドドロップイベント
scAb.Panel2.DragDrop += (sender, e) =>
{
if (e.Data == null) return;
var fd = e.Data.GetData(DataFormats.FileDrop);
if (fd == null) return;
string? path = ((string[])fd)[0];
picboxB.Image?.Dispose();
using(var fs = new FileStream(path, FileMode.Open))
{
picboxB.Image = new Bitmap(fs);
}
};
// 実行ボタンをクリック
execButton.Click += (s, e) =>
{
if (picboxA.Image is null)
{
MessageBox.Show("A画像が未設定", "警告");
return;
}
if (picboxB.Image is null)
{
MessageBox.Show("B画像が未設定", "警告");
return;
}
if (picboxA.Image.Width != picboxB.Image.Width || picboxA.Image.Height != picboxB.Image.Height)
{
MessageBox.Show("AB画像の幅、高さが異なる", "警告");
return;
}
if (picboxA.Image.PixelFormat != picboxB.Image.PixelFormat)
{
MessageBox.Show("AB画像がPixelFormatが異なる", "警告");
return;
}
byte[] abytes = BitmapToBytes((Bitmap)picboxA.Image);
byte[] bbytes = BitmapToBytes((Bitmap)picboxB.Image);
byte[] cbytes = new byte[bbytes.Length];
int stride = cbytes.Length / picboxA.Image.Height;
int channel = Bitmap.GetPixelFormatSize(picboxA.Image.PixelFormat) / 8;
for (int ch = 0; ch < channel; ch++)
{
for (int y = 0; y < picboxA.Image.Height; y++)
{
for (int x = 0; x < picboxA.Image.Width; x++)
{
int a = abytes[y * stride + x * channel + ch];
int b = bbytes[y * stride + x * channel + ch];
int c = Math.Abs(a - b);
if (ch == 3)
{
// アルファチャンネルは強制的に255
cbytes[y * stride + x * channel + ch] = 255;
}
else
{
cbytes[y * stride + x * channel + ch] = (byte)c;
}
}
}
}
Bitmap result = BytesToBitmap(cbytes, picboxA.Image.Width, picboxA.Image.Height, picboxA.Image.PixelFormat);
picboxResult.Image?.Dispose();
picboxResult.Image = result;
};
}
}
実行
dotnet run
使い方
A:及びB:に画像をドラッグアンドドロップ。
実行ボタンを押す
実行ボタンを押す
二つの画像に異なる部分(差)が無い場合、全面黒(0)になります。
感想
最初、差分画像が表示されず全面灰色になってしまい悩みました。原因はアルファチャンネルの差分まで計算したため結果が0で透明になっていたためでした。とりあえずアルファチャンネルは強制的に255を埋めるようにしています。
コメント