【C#/WPF】WriteableBitmapで点を打って、テレビの放送終了後の砂嵐をつくる(タイマーで処理)

# やりたいこと
任意の場所に点を打って、画像を作成したい。

# WriteableBitmapを使う

WriteableBitmapは、BitmapSourceクラスから派生したクラス。
他にBitmapSourceから派生したクラスに、BitmapImage、RenderTargetBitmap、TransformedBitmapなどがある。([msdocs](https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.media.imaging.bitmapsource?view=netframework-4.8)/[自分のページ](https://qiita.com/tera1707/items/1f541e47540d0f32fa92))

それぞれ派生クラスには役割があるが、WriteableBitmapは、ピクセル単位で任意の点を打つことができる。
想像するに、写真やwebカメラ画像の上にWriteableBitmapで書いた透明の領域と半透明の黒い領域をを含む画像をかぶせて、「無効領域」などを表現したりできそう。

# 試しに作成したサンプル(ViewModelのみ抜粋)

試しに、掲題の「砂嵐」を出すサンプルを作成した。

```cs
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;

namespace WpfApp1
{
    class ViewModel : BindingBase
    {        
        public WriteableBitmap MyWBitmap { get; set; } = new WriteableBitmap(1280, 720, 96, 96, PixelFormats.Pbgra32, null);

        DispatcherTimer _drawingTimer = new DispatcherTimer();

        public ViewModel()
        {
            // 描画タイマー作動
            _drawingTimer.Interval = new TimeSpan(0, 0, 0, 0, 33);
            _drawingTimer.Tick += _timer_draw_Tick;
            _drawingTimer.Start();
        }

        // 画面の更新はメインスレッドで実施しないといけないので、Task等ではできない。
        // (具体的には、MyWBitmapに値を入れるところを別スレッドにはできない)
        private void _timer_draw_Tick(object sender, EventArgs e)
        {
            int width = (int)1280;
            int height = (int)720;

            // 計算用のバイト列の準備
            int pixelsSize = (int)(width * height * 4);
            byte[] pixels = new byte[pixelsSize];

            // 乱数の準備
            Random rnd = new System.Random();
            int rndMax = 256;                    // 0～256の乱数を取得

            // バイト列に色情報を入れる
            for (int i = 0; i < width * height; i++)
            {
                pixels[4 * i] = (byte)rnd.Next(rndMax);        //blue;
                pixels[4 * i + 1] = (byte)rnd.Next(rndMax);    // green;
                pixels[4 * i + 2] = (byte)rnd.Next(rndMax);    // red;
                pixels[4 * i + 3] = (byte)255;                 //alpha
            }

            // バイト列をBitmapImageに変換する
            int stride = width * 4; // 一行あたりのバイトサイズ(Pbgra32だから、1ピクセル当たり4バイトとなるので、ピクセル数に4を掛ける)
            MyWBitmap.WritePixels(new Int32Rect(0, 0, width, height), pixels, stride, 0, 0);
        }
    }
}
```

# ViewModelの概要
- コードの「**// バイト列に色情報を入れる**」のところで、ピクセルのデータを作成している。forループ1回分が、1ピクセルのイメージ。
- 1ピクセル当たり4バイトを使っていて、それぞれ前から[青][緑][赤][アルファ]を示す。
- なので、透明の領域を作りたい場合は、各ピクセルの4バイト目のアルファ値を0にすればよい。
- ((未検証)各ピクセルが4バイトなのは、PixelFormats.Pbgra32の場合で、ほかの形式を指定した場合は1ピクセル当たりのバイト数が変わるっぽい[(こちら参照)](https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.media.pixelformats?view=netframework-4.8)
- 作ったバイト列を作ったWriteableBitmapのWritePixelsメソッドに渡せば、画像が更新される。
- **砂嵐**は、byte列に値を入れるときに、乱数を入れるだけで作れる。



# Xaml側

```cs
<Window x:Class="WpfApp1.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"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Viewbox Grid.Row="0" Grid.Column="1">
            <Image Source="{Binding MyWBitmap}"/>
        </Viewbox>
    </Grid>
</Window>
```

# 動かした画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/dcb75f09-9050-c360-adcc-2361be7126bd.png)

# unsafeなし
ここでは、byte配列を普通に作って、それをWritePixelsに渡す形で実現したのでunsafeは出てこなかったが、unsafeにして、byteのポインタを使って実現する方法もあるらしい。
C++のライブラリで作成された画像データのbyte配列をC#で受け取るときにそういうことをしているのを見たことがある。
参考：https://blogs.yahoo.co.jp/gogowaten/15497771.html

# Taskで処理したバージョン
[こちら](https://qiita.com/tera1707/items/82ebc27d4957ddd5b7ad)。
Taskは別スレッドで処理が行われるので、Taskの中でも、UI関連の処理をするときだけUIスレッドに戻すということをしないといけない。

# コード
https://github.com/tera1707/WPF-/tree/master/022_WriteableBitmap

# 参考
WriteableBitmap Class 
https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.media.imaging.writeablebitmap?view=netframework-4.8

PixelFormats Class 
https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.media.pixelformats?view=netframework-4.8

C#で画像を描いてみた（WPFでWritableBitmap編）
https://water2litter.net/gin/?p=984