LoginSignup
7
11

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-04-19

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

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

やりたいこと

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

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

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

流れ

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

  • RenderTargetBitmapで、画面から画像を取り込む
  • 'DrawingVisual'で'DrawingContext'を取得
  • 取得した'DrawingContext'を通して'DrawingVisual'を編集する。(下記をする)
    • まず元の画像をはりつける
    • その後、好きなお絵かきをする
  • いろいろ書き込んだ'DrawingVisual'を、RenderTargetBitmapに描画する
  • RenderTargetBitmapBitmapEncoder派生クラスと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://docs.microsoft.com/ja-jp/dotnet/framework/wpf/graphics-multimedia/using-drawingvisual-objects

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

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

わからなかったこと

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

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

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

image.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

7
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
11