もくじ
→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'を使用してそういうことをしたことがある(こちら)が、'System.Drawing.Bitmap'はWinFormで使われていた少々古いやり方だという個人の認識があるので、WPFっぽい'System.Windows.Imaging'をつかったやり方でやってみたい。
画面の表示を取り込んで、そこに図形等を書き込んで、ファイルに保存する
流れ
下記のようなことをする。
-
RenderTargetBitmap
で、画面から画像を取り込む - 'DrawingVisual'で'DrawingContext'を取得
- 取得した'DrawingContext'を通して'DrawingVisual'を編集する。(下記をする)
- まず元の画像をはりつける
- その後、好きなお絵かきをする
- いろいろ書き込んだ'DrawingVisual'を、
RenderTargetBitmap
に描画する -
RenderTargetBitmap
をBitmapEncoder
派生クラスとBitmapFrame
を組み合わせてファイルに保存
コード
<Grid>
<Image Name="MyImage" Source="input.jpg" VerticalAlignment="Top"/>
</Grid>
// 元になる画像を画面から読み込む
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する
コード
<Grid>
<Image Name="MyImage" VerticalAlignment="Top"/>
</Grid>
// 元になる画像を読み込む
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部品の先祖クラスだから重たそう、というイメージだったが、下記をみるとそうではなさそう、なのか?
→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を使ってファイル保存、ということをしないと、ファイル保存できない。
(いまのところ、そういうやり方しか見つけられていない)
もっとシンプルに画像を保存することはできないのか?
関連クラスの継承関係メモ
クラスの継承関係がよくわからなくなったので、下記にまとめてみた。
まとめてみると、少しだけわかりやすくなった気がした。
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