午後わてんのブログ

ベランダ菜園とWindows用アプリ作成(WPFとC#)

WPF、Canvasの中に画像として保存したい要素が回転や拡大など変形されていてもOKな方法

 
Canvasの中にある画像として保存したい要素が回転や拡大など変形されていてもOKな方法
    Private Sub Test2ImageFile(obj As FrameworkElement, parentPanel As Panel)
        '変形した要素がぴったり収まるRectを取得
        Dim gt As GeneralTransform = obj.TransformToVisual(parentPanel)
        Dim r As Rect = gt.TransformBounds(New Rect(0, 0, obj.ActualWidth, obj.ActualHeight))
        '要素のVisualBrush(ブラシ)作成、引き伸ばしされないようにStretch.Noneを指定
        Dim vb As New VisualBrush(obj) With {.Stretch = Stretch.None}
        '四角枠にブラシを使って塗る
        Dim dv As New DrawingVisual
        Using dc As DrawingContext = dv.RenderOpen
            dc.DrawRectangle(vb, Nothing, New Rect(New Size(r.Width, r.Height)))
        End Using
        'Bitmap作成してRender
        Dim rtb As New RenderTargetBitmap(r.Width, r.Height, 96, 96, PixelFormats.Pbgra32)
        rtb.Render(dv)

        'Bitmapをpng形式の画像で保存
        Dim enc As New PngBitmapEncoder
        enc.Frames.Add(BitmapFrame.Create(rtb))
        'Using fs As New IO.FileStream("testImage.png", IO.FileMode.Create)
        Using fs As New IO.FileStream(obj.Name & ".png", IO.FileMode.Create)
            enc.Save(fs)
        End Using
    End Sub
 
イメージ 2
MyCanvas(Canvas)に表示しているMyCyanBorder(Border)を画像として保存するときは
Call Test2ImageFile(MyCyanBorder, MyCanvas)
ってするとpng形式の画像で保存される
イメージ 1
保存された画像(MyCyanBorder)
 
イメージ 3
 
 
今回のアプリ

github.com

画像ファイルも同梱したけ同国かどうかはわからん

 

デザイン画面
ヤフーブログのかんたんモードにXAMLを書くと投稿エラーになるから画像で

f:id:gogowaten:20191031132543p:plain

 
VBコード
Class MainWindow

   '現在日時を文字列にして取得
    Private Function GetNowString() As String
        Dim str As String = Now.ToString
        str = Replace(str, "/", "")
        str = Replace(str, ":", "")
        str = Replace(str, " ", "_")
        Return str
    End Function
   '対象がぴったり収まるRect取得
    Private Function GetRect(obj As FrameworkElement)
        Return obj.TransformToVisual(MyCanvas).TransformBounds(New Rect(New Size(obj.ActualWidth, obj.ActualHeight)))
    End Function
   'Bitmapをpng画像で保存
    Private Sub Bitmap2pngFile(bmp As BitmapSource, filePath As String)
        Dim enc As New PngBitmapEncoder
        enc.Frames.Add(BitmapFrame.Create(bmp))
        Using fs As New IO.FileStream(filePath, IO.FileMode.Create)
            enc.Save(fs)
        End Using
    End Sub
   'RenderTargetBitmapを使って対象をBitmapにして保存
    Private Sub SaveImage(obj As FrameworkElement)
        Dim r As Rect = GetRect(obj)
        Dim rtb As New RenderTargetBitmap(r.Width, r.Height, 96, 96, PixelFormats.Pbgra32)
        rtb.Render(obj)
        Dim str As String = GetNowString() & obj.Name & ".png"
        Call Bitmap2pngFile(rtb, str)
    End Sub
   'VisualBrushとRenderTargetBitmapを使って対象をBitmapにして保存
    Private Sub SaveImageVisualBrush(obj As FrameworkElement)
        Dim r As Rect = GetRect(obj)
        Dim vb As New VisualBrush(obj) With {.Stretch = Stretch.None}
        Dim dv As New DrawingVisual
        Using dc As DrawingContext = dv.RenderOpen
            dc.DrawRectangle(vb, Nothing, New Rect(New Size(r.Width, r.Height)))
        End Using
        Dim rtb As New RenderTargetBitmap(r.Width, r.Height, 96, 96, PixelFormats.Pbgra32)
        rtb.Render(dv)
        Dim str As String = GetNowString() & obj.Name & ".png"
        Call Bitmap2pngFile(rtb, str)
    End Sub

    Private Sub TestSave1()
        Call SaveImage(MyOrangeBorder)
        Call SaveImage(MyRedBorder)
        Call SaveImage(MyPurpleBorder)
        Call SaveImage(MyPinkBorder)
        Call SaveImage(MyCyanBorder)
    End Sub
    Private Sub TestSave2()
        Dim r As Rect = GetRect(MyOrangeBorder)
        Dim rtb As New RenderTargetBitmap(r.Width, r.Height, 96, 96, PixelFormats.Pbgra32)
        rtb.Render(MyOrangeCanvas)
        Dim str As String = GetNowString() & "MyOrangeBorder.png"
        Call Bitmap2pngFile(rtb, str)

        r = GetRect(MyRedBorder)
        rtb = New RenderTargetBitmap(r.Width, r.Height, 96, 96, PixelFormats.Pbgra32)
        rtb.Render(MyRedCanvas)
        str = GetNowString() & "MyRedborder.png"
        Call Bitmap2pngFile(rtb, str)

        r = GetRect(MyPurpleBorder)
        rtb = New RenderTargetBitmap(r.Width, r.Height, 96, 96, PixelFormats.Pbgra32)
        rtb.Render(MyPurpleCanvas)
        str = GetNowString() & "MyPurpleborder.png"
        Call Bitmap2pngFile(rtb, str)

        r = GetRect(MyPinkBorder)
        rtb = New RenderTargetBitmap(r.Width, r.Height, 96, 96, PixelFormats.Pbgra32)
        rtb.Render(MyPinkCanvas)
        str = GetNowString() & "MyPinkborder.png"
        Call Bitmap2pngFile(rtb, str)

    End Sub
    Private Sub TestSave3()
        Call SaveImageVisualBrush(MyOrangeBorder)
        Call SaveImageVisualBrush(MyRedBorder)
        Call SaveImageVisualBrush(MyPurpleBorder)
        Call SaveImageVisualBrush(MyPinkBorder)
        Call SaveImageVisualBrush(MyCyanBorder)

        Call SaveImageVisualBrush(MyPinkCanvas)

    End Sub
    Private Sub MainWindow_Initialized(sender As Object, e As EventArgs) Handles Me.Initialized
        AddHandler btnSave1.Click, AddressOf TestSave1
        AddHandler btnSave2.Click, AddressOf TestSave2
        AddHandler btnSave3.Click, AddressOf TestSave3

    End Sub
End Class
 
イメージ 5
右半分に背景色ベージュのCanvasに5つのBorderを回転表示している
それぞれのBorderの表示を変えていて
オレンジのBorder
イメージ 6
x,y=(10,20)のCanvasの中に表示、回転はしていない
 
赤のBorder

f:id:gogowaten:20191031132756p:plain

(50,50)のCanvasの中に表示、Borderを10度回転
 
紫のBorder

f:id:gogowaten:20191031132810p:plain

位置指定なしのCanvasの中に表示、Borderを50度回転、位置は0,20
 
ピンクのBorder

f:id:gogowaten:20191031132824p:plain

位置120,20のCanvasの中に表示、Borderを50度回転
 
水色のBorder

f:id:gogowaten:20191031132837p:plain

Canvasは無しでそのまま表示、Borderを120度回転、位置は20,20
 
 
 
ボタンで保存
それぞれのBorderやその一個上のCanvasを画像として保存する
Save1ボタン
イメージ 11
保存するBitmap作成をRenderTargetBitmapのRenderだけで行う方法
1番簡単だけど結果はイマイチでこうなる
イメージ 12
回転していないオレンジだけ期待どおりで、それ以外は違う
 
 
Save2ボタン
イメージ 13
Save1はBorderをRenderしていたけど
Save2はBorderの一個上のCanvasをRenderしているだけでそれ以外は全く同じ
結果は期待はずれ
イメージ 14
どうやら一番上のMyCanvasの左上を基準にBitmapが作成されているみたいで大幅にずれている
 
 
Save3
イメージ 15
このページの一番上に書いたOKな方法と同じ
VisualBrushとDrawingVisualを使った方法
結果はOKな方法なので期待どおり
イメージ 16
真ん中のピンクBorderが回転されていないのは、回転指定されているのはBorderじゃなくてその一個上のCanvasだからで、その一個上のCanvasを保存した左上のピンクBorderは正しく保存されている
 
 
参照したところ
Nine Works WPFで要素を画像として保存する
http://nineworks2.blog.fc2.com/blog-entry-13.html

WPF - CanvasをBitmapに変換して画像ファイルとして保存する - Qiita

qiita.com



UWP で UIElement の外観をそのまま画像化して PNG ファイルに保存したい - しっぽを追いかけて

matatabi-ux.hateblo.jp

ありがとうございます
 
 
 
 
この方法までたどり着くのに1ヶ月くらいかかった、取り掛かる前は1日でできると思っていたんだけどねえw
変形させなければ参照先の方法でいいんだけどPixtack紫陽花ではできていたことができなくなるのは嫌だからっていろいろ試していたら一ヶ月経っていたw
 
文字列の描画も少し試してみてこんな感じ

f:id:gogowaten:20191031132859p:plain

WindowsFormのPixtack紫陽花では縁取りがうまく書けなかったけどWPFは縁取りの指定するところがあってあちこち見ながら試したらできた!
 
 
今回のコード
 
 
前回のVB.NETWPFの記事は1ヶ月前
画像をくっきり表示させたい(ぼやけるのがイヤな)とき、EdgeModeとScalingMode、WPF ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/14912530.html
次は翌日
 
 4年後

 

6年後、今回のをC#で書いただけ