[C#] 画像に対していろいろ行う(System.Windows.Imaging版)

**もくじ**
→https://qiita.com/tera1707/items/4fda73d86eded283ec4f

**画像に対していろいろやるシリーズ**

- 画像に対していろいろ行う(System.Drawing.Bitmap版)  
https://qiita.com/tera1707/items/385c9baca83f7c08e5e1
- 画像に対していろいろ行う(System.Windows.Imaging版)  
https://qiita.com/tera1707/items/1dfef61ccab7f7e3a381

# やりたいこと
WPFアプリで、画像ファイルやアプリの表示画像を取り込んで、そこに四角やテキストを書き込んで、画面に表示したりファイルに保存したりしたい。

以前、'System.Drawing.Bitmap'を使用してそういうことをしたことがある[(こちら)](https://qiita.com/tera1707/items/385c9baca83f7c08e5e1)が、'System.Drawing.Bitmap'はWinFormで使われていた少々古いやり方だという個人の認識があるので、WPFっぽい'System.Windows.Imaging'をつかったやり方でやってみたい。

# 画面の表示を取り込んで、そこに図形等を書き込んで、ファイルに保存する

## 流れ

下記のようなことをする。

- `RenderTargetBitmap`で、画面から画像を取り込む
- 'DrawingVisual'で'DrawingContext'を取得
- 取得した'DrawingContext'を通して'DrawingVisual'を編集する。(下記をする)
    - まず元の画像をはりつける
    - その後、好きなお絵かきをする
- いろいろ書き込んだ'DrawingVisual'を、`RenderTargetBitmap`に描画する
- `RenderTargetBitmap`を`BitmapEncoder`派生クラスと`BitmapFrame`を組み合わせてファイルに保存

## コード

```画面.xaml
<Grid>
    <Image Name="MyImage" Source="input.jpg" VerticalAlignment="Top"/>
</Grid>
```

```c#.cs
// 元になる画像を画面から読み込む
var image = new RenderTargetBitmap((int)MyImage.ActualWidth, (int)MyImage.ActualHeight, 96, 96, PixelFormats.Pbgra32);
image.Render(MyImage);

// 元画像に色々書き込む
DrawingVisual dv = new DrawingVisual();
using (DrawingContext drawContent = dv.RenderOpen())
{
    drawContent.DrawImage(image, new System.Windows.Rect(0, 0, image.PixelWidth, image.PixelHeight));// 画像を置く
    drawContent.DrawText(new FormattedText("表示したいstring", System.Globalization.CultureInfo.CurrentUICulture, System.Windows.FlowDirection.LeftToRight, new Typeface("Verdana"), 100, Brushes.Gold, VisualTreeHelper.GetDpi(this).PixelsPerDip), new System.Windows.Point(100, 100));
    drawContent.DrawRectangle(Brushes.Red, new Pen(Brushes.Black, 3), new Rect(0, 0, 200, 200));    // 四角を描く
    drawContent.DrawEllipse(Brushes.Yellow, new Pen(Brushes.Green, 3), new Point(50, 50), 10, 10);  // 丸を描く
}

// いろいろ書いたDrawingVisualを、RenderTargetBitmap(BitmapSourceの子クラス)に取り込む
var bmp = new RenderTargetBitmap((int)image.Width, (int)image.Height, 96, 96, PixelFormats.Pbgra32);
bmp.Render(dv);

// 画面に表示もできる
MyImage.Source = bmp;

// そいつをファイルに保存
var encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bmp));

using (var stream = new FileStream("output2.jpeg", FileMode.Create))
{
    encoder.Save(stream);
}
```

上のやり方では、元画像にいろいろ書き込むのは`DrawingVisual`を使ってやっている。
が、別のやり方もあるようなので、下に挙げておく。


# 画像ファイルを取り込んで、そこに図形等を書き込んで、画面に表示する

## 流れ

- `BitmapImage`で、ファイルから画像を取り込む
- 'DrawingGroup'で'DrawingContext'を取得
- 取得した'DrawingContext'を通して'DrawingGroup'を編集する。(下記をする)
    - まず元の画像をはりつける
    - その後、好きなお絵かきをする
    - ※Openだと新規書き込み、Appendだと追加書き込みをする。
- いろいろ書き込んだ'DrawingContext'を、`DrawingImage`のコンストラクタにわたして`DrawingImage`をつくる
- xaml上の`Image`コントロールの`Source`プロパティに、作った`DrawingImage`をsetする

## コード

```画面.xaml
<Grid>
    <Image Name="MyImage" VerticalAlignment="Top"/>
</Grid>
```

```c#.cs
// 元になる画像を読み込む
var uri = new Uri(@"input.jpg", UriKind.Relative);
var image = new BitmapImage(uri);

DrawingGroup drawingGroup = new DrawingGroup();
using (DrawingContext drawContent = drawingGroup.Open())
{
    // 画像を書いて、その上にテキストを書く
    drawContent.DrawImage(image, new System.Windows.Rect(0, 0, image.PixelWidth, image.PixelHeight));
    drawContent.DrawText(new FormattedText("表示したいstring", System.Globalization.CultureInfo.CurrentUICulture, System.Windows.FlowDirection.LeftToRight, new Typeface("Verdana"), 100, Brushes.Gold, VisualTreeHelper.GetDpi(this).PixelsPerDip), new System.Windows.Point(100, 100));
}
using (DrawingContext drawContent = drawingGroup.Append())
{
    // 追加でいろんなものを書き込む
    drawContent.DrawRectangle(Brushes.Red, new Pen(Brushes.Black, 3), new Rect(0, 0, 200, 200));    // 四角を描く
    drawContent.DrawEllipse(Brushes.Yellow, new Pen(Brushes.Green, 3), new Point(50, 50), 10, 10);  // 丸を描く
}

// 色々書き込んだものを使って、DrawingImageをつくって画面表示にセット
MyImage.Source = new DrawingImage(drawingGroup);
```

最初例ではDrawingVisualの`dv.RenderOpen()`で、DrawingContextを作成していたが、こっちはDrawingGroupの`drawingGroup.Open()`でDrawingContextを作成している。

---

`Visual`とつくので、なにかUI部品の先祖クラスだから重たそう、というイメージだったが、下記をみるとそうではなさそう、なのか？

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/ef736c7b-9718-c3e9-4999-040802598802.png)
→https://docs.microsoft.com/ja-jp/dotnet/framework/wpf/graphics-multimedia/using-drawingvisual-objects

あと、[以前の記事](https://qiita.com/tera1707/items/1f541e47540d0f32fa92)でもちょっとメモした、System.Windows.Imagingの画像関係のクラス(BitmapSourceとそれを継承したクラス)の親戚のようなクラスで、`DrawingImage`というのが出てきた。

こいつも、BitmapSourceと同じ`ImageSource`を継承しているので、ImageのSourceにセットして画面表示に使うことができる。

# わからなかったこと

不思議なのが、BitmapSource側には、ファイルの保存に使える「BitmapFrame」というクラスがあるが、DrawingImage側にはそういうのがない。
そのため、一旦、上のコードでいうところの`DrawingVisual`に変換して、そこからRenderTargetBitmapに直して、そこからBitmapFrameを使ってファイル保存、ということをしないと、ファイル保存できない。
(いまのところ、そういうやり方しか見つけられていない)
もっとシンプルに画像を保存することはできないのか？

# 関連クラスの継承関係メモ

クラスの継承関係がよくわからなくなったので、下記にまとめてみた。
まとめてみると、少しだけわかりやすくなった気がした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/878ceaa6-0da5-1ac6-d49d-47e35b2ddaba.png)


# gitlab

自分の実験コード
https://github.com/tera1707/WPF-/tree/master/034_AboutBitmapDotNetCore


# 参考
WriteableBitmapに文字を書き込んで画面に表示する方法
http://kskhsn15.hatenadiary.jp/entry/20130318/1363622850

Convert DrawingImage to BitmapImage
https://stackoverflow.com/questions/14387350/convert-drawingimage-to-bitmapimage

DrawingVisual
https://docs.microsoft.com/ja-jp/dotnet/framework/wpf/graphics-multimedia/using-drawingvisual-objects
https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.media.drawingvisual?view=netframework-4.8

DrawingImage
https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.media.drawingimage?view=netframework-4.8
https://docs.microsoft.com/ja-jp/dotnet/framework/wpf/graphics-multimedia/how-to-use-a-drawing-as-an-image-source