WriteableBitmapExその3
前回に引き続きWriteableBitmapExで色々やってみた。
基本的な画像処理とかをWPFで実装してみました。
ベースとなるXAMLは前回と同じ。
コードビハインドは、ちょこっと訂正。
XAML
<Window x:Class="WriteableBitmapExTest.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Title="MainWindow" mc:Ignorable="d" d:DesignHeight="350" d:DesignWidth="525" SizeToContent="WidthAndHeight"> <Grid> <Image Name="imgTarget" Stretch="None" /> </Grid> </Window>
コードビハインド
前回はコンストラクタで画像の読み込みや描画をしてたけど、
OnInitializeをオーバーライドして、ここで各種初期化・読み込み・描画をするように修正。
※コンストラクタの中で、リソースの画像を読もうとしたら、例外が出ることがあったため。。。
WriteableBitmapのインスタンスとして、以下の二つをメンバにしています。
original・・・元画像用バッファ
processed・・処理結果格納用のインスタンス
namespace WriteableBitmapExTest { /// <summary> /// MainWindow.xaml の相互作用ロジック /// </summary> public partial class MainWindow : Window { WriteableBitmap original; WriteableBitmap processed; public MainWindow() { InitializeComponent(); } protected override void OnInitialized(EventArgs e) { base.OnInitialized(e); // 画像読み込み var bmp = new BitmapImage(new Uri("pack://application:,,,/Images/Koala.jpg")); original = BitmapFactory.ConvertToPbgra32Format(bmp); // 処理結果格納用インスタンス準備 processed = BitmapFactory.New(original.PixelWidth, original.PixelHeight); // 画像処理 Processing(); // 描画結果をコントロールに反映 imgTarget.Source = processed; } /// <summary> /// 画像処理ルーチン /// </summary> private void Processing() { // ここに画像への処理を書いてきます。 } } }
以下では、この中のProcessingメソッドに色々と処理を書いてます。
全ピクセルに対して、同じ処理を行うもの
全ピクセルに対して同じ処理を行う場合には、WriteableBitmapExで拡張メソッドとして用意されている、FoaEachメソッドが使えます。
モノクロ化
private void Processing() { // ここに画像への処理を書いてきます。 processed = original; using (processed.GetBitmapContext()) { processed.ForEach((x, y, c) => { byte v = (byte)((c.R + c.G + c.B) / 3); return Color.FromArgb(255, v, v, v); } ); } }
輝度値でモノクロ化するなら、以下の式を使う
byte v = (byte)(0.299*c.R + 0.587*c.G + 0.114*c.B);
GetBrightnessなんてメソッドがあるので、これ使った方が早いかも。
byte v = original.GetBrightness(x, y);
ネガポジ反転
private void Processing() { // ここに画像への処理を書いてきます。 processed = original; using (processed.GetBitmapContext()) { processed.ForEach((x, y, c) => Color.FromArgb(255, (byte)(255-c.R), (byte)(255-c.G), (byte)(255-c.B))); } }
で、このくらいの処理は、ライブラリ内にメソッドとしてすでに用意されてたりする。
var inverted = wb.Invert();
コントラスト調整
private void Processing() { // ここに画像への処理を書いてきます。 processed = original; using (processed.GetBitmapContext()) { float val = 1.2f; processed.ForEach((x, y, c) => { float r = 0.5f + val * (c.ScR - 0.5f); float g = 0.5f + val * (c.ScG - 0.5f); float b = 0.5f + val * (c.ScB - 0.5f); Color result = Color.FromScRgb(1, r, g, b); result.Clamp(); return result; } ); } }
フィルター処理
ぼかし処理
隣接する8近傍の値の平均を取るだけのぼかしフィルタ
private void Processing() { // ここに画像への処理を書いてきます。 processed = original; using (processed.GetBitmapContext()) { processed.ForEach((x, y) => { int count = 0; Color Sum = Color.FromScRgb(1, 0, 0, 0); for (int dy = -1; dy < 2; dy++) for (int dx = -1; dx < 2; dx++) { // 画像の領域をはみ出してしまう場合にはスキップ if (x + dx < 0 || original.PixelWidth <= x + dx || y + dy < 0 || original.PixelHeight <= y + dy) continue; Color c = original.GetPixel(x + dx, y + dy); Sum += c; count++; } Sum = Color.FromScRgb(1, Sum.ScR / count, Sum.ScG / count, Sum.ScB / count); return Sum; } ); } }
Convoluteメソッド
で、やっぱりこういうフィルター処理用にConvoluteというメソッドが用意されてたりする。
二次元配列で、フィルターの内容を定義して、Convoluteメソッドに渡すだけ。
もう至れり尽くせりなライブラリです。こんな楽しちゃっていいのかな、と思うくらい。。
さっきのぼかし処理は、以下のように書けます。
private void Processing() { // ここに画像への処理を書いてきます。 processed = original; using (processed.GetBitmapContext()) { int[,] kernel = { {1, 1, 1}, {1, 1, 1}, {1, 1, 1}, }; processed = original.Convolute(kernel); } }
ラプラシアン・フィルタ
このConvoluteメソッド、行列の全要素の和で割るという処理が内部にあるため、ラプラシアン・フィルタのような行列を渡すと例外を吐いてしまいます。
で、引数が3つのオーバーロード版があるので、そちらを使います。
・第二引数に行列全体の和(1を渡せば、渡した行列を使った計算結果そのままの値になるっぽい。)
・第三引数は、バイアス値。すべてのピクセルにこの値が加算されてかえって来ます。
private void Processing() { // ここに画像への処理を書いてきます。 processed = original; using (processed.GetBitmapContext()) { int[,] kernel = { {-1, -1, -1}, {-1, 8, -1}, {-1, -1, -1}, }; processed = original.Convolute(kernel, 1, 0); // ↑のような変換をすると、αチャンネルの値が0になってしまって表示されないので、強制的に書き換え。 processed.ForEach((x, y, c) => Color.FromArgb(255, c.R, c.G, c.B)); } }
エンボス処理
private void Processing() { // ここに画像への処理を書いてきます。 processed = original; using (processed.GetBitmapContext()) { int[,] kernel = { {-1, 0, 1}, {-1, 0, 1}, {-1, 0, 1}, }; processed = original.Convolute(kernel, 1, 127); // ↑のような変換をすると、αチャンネルの値が0になってしまって表示されないので、強制的に書き換え。 processed.ForEach((x, y, c) => Color.FromArgb(255, c.R, c.G, c.B)); } }