午後わてんのブログ

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

WPF、画像をくっきり表示させたい(ぼやけるのがイヤな)とき、EdgeModeとScalingMode

 
前回は図形に対するアンチエイリアスの切り替えだった
今回は画像に対するアンチエイリアスの切り替えというかぼやけるのを切り替え
 
特に設定しないと拡大や回転表示させたときにはアンチエイリアスが有効な状態で表示される、くっきり表示させたいときは
EdgeModeをAliased
ScalingModeをNearestNeighbor
 
MyImageなら
RenderOptions.SetEdgeMode(MyImage, EdgeMode.Aliased)
RenderOptions.SetBitmapScalingMode(MyImage, BitmapScalingMode.NearestNeighbor)
 

動作
 
テストに使う画像は白背景に黒枠が3つ
イメージ 1
枠の太さは1ピクセル
画像の大きさは16x16ピクセル
形式はBMP
 
 
WPFのImageコントロールを使ってこの画像を表示
イメージ 2
特に何も設定しないで表示すると普通(期待通り)に表示される
 
3倍に拡大表示
イメージ 3
RenderTransformのScaleTransformを3にして3倍に拡大表示
 
ScalingModeをNearestNeighborに変更して拡大
イメージ 4
くっきりになる
ScalingModeの初期設定値はUnspecified(未定義)なんだけどイメージ 5
実際はLinearみたい
 
回転
ScalingModeをUnspecifiedに戻してから
10度回転
イメージ 6
ScalingModeをNearestneighborに変更
イメージ 7
くっきりになるぶんガタガタになるけど
よく見ると外側だけはなめらか
 
EdgeModeをAliasedに変更
イメージ 8
これで全部くっきりになる
 
なので、くっきり表示させたいときは
EdgeModeをAliased
ScalingModeをNearestNeighbor
 
写真画像の場合
イメージ 11
 
 
 
UseLayoutRoundingの切り替え
0.1ポイント単位で移動させると違いがわかる

0.1ポイント単位での移動時の表示
UseLayoutRoundingをTrueにすると
0.1ポイント単位で移動させても1ポイント(ピクセル)単位での移動になるみたい
 
 
 
XAMLデザイン画面

f:id:gogowaten:20191031124142p:plain

 
VBコード
Class MainWindow
    Private MyImage As Image

    Private Sub MainWindow_Initialized(sender As Object, e As EventArgs) Handles Me.Initialized
        AddHandler btnEdge.Click, AddressOf btnEdge_Click
        AddHandler btnUseLayoutRounding.Click, AddressOf btnUseLayoutRounding_Click
        AddHandler btnSnapsToDevicePixels.Click, AddressOf btnSnapsToDevicePixels_Click
        AddHandler btnScalingMode.Click, AddressOf btnNearestNeighbor_Click
        AddHandler btnReset.Click, AddressOf btnReset_Click

        Dim bi As New BitmapImage(New Uri("D:\ブログ用\テスト用画像\border_round.bmp"))

        MyImage = New Image With {
            .Source = bi,
            .RenderTransform = GetRenderTransform(),
            .RenderTransformOrigin = New Point(0.5, 0.5)
        }

        Canvas.SetLeft(MyImage, 50) : Canvas.SetTop(MyImage, 50)
        MyCanvas.Children.Add(MyImage)

        Call MySetBinding()
    End Sub
    Private Function GetRenderTransform() As Transform
        Dim tg As New TransformGroup
        With tg.Children
            .Add(New ScaleTransform(1.0, 1.0))
            .Add(New SkewTransform)
            .Add(New RotateTransform)
        End With
        Return tg
    End Function

    'Binding
    Private Sub MySetBinding()
        Dim b As Binding

        b = New Binding With {
            .Source = MyImage,
            .Path = New PropertyPath(RenderOptions.EdgeModeProperty),
            .StringFormat = "EdgeMode = {0}"
        }
        tbEdge.SetBinding(TextBlock.TextProperty, b)

        b = New Binding With {
            .Source = MyImage,
            .Path = New PropertyPath(UseLayoutRoundingProperty),
            .StringFormat = "UseLayoutRounding = {0}"}
        tbUseLayout.SetBinding(TextBlock.TextProperty, b)

        b = New Binding With {
            .Source = MyImage,
            .Path = New PropertyPath(SnapsToDevicePixelsProperty),
            .StringFormat = "SnapsToDevicePixels = {0}"}
        tbSnapTo.SetBinding(TextBlock.TextProperty, b)

        b = New Binding With {
            .Source = MyImage,
            .Path = New PropertyPath(RenderOptions.BitmapScalingModeProperty),
            .StringFormat = "ScalingMode = {0}"}
        tbScalingMode.SetBinding(TextBlock.TextProperty, b)

        Dim st As ScaleTransform = GetTransform(GetType(ScaleTransform))
        b = New Binding With {.Source = st, .Path = New PropertyPath(ScaleTransform.ScaleXProperty),
            .StringFormat = "ScaleX = {0}"}
        sldScaleX.SetBinding(Slider.ValueProperty, b)
        tbScaleX.SetBinding(TextBlock.TextProperty, b)
        b = New Binding With {.Source = st, .Path = New PropertyPath(ScaleTransform.ScaleYProperty),
            .StringFormat = "ScaleY = {0}"}
        sldScaleY.SetBinding(Slider.ValueProperty, b)
        tbScaleY.SetBinding(TextBlock.TextProperty, b)

        Dim ro As RotateTransform = GetTransform(GetType(RotateTransform))
        b = New Binding With {.Source = ro, .Path = New PropertyPath(RotateTransform.AngleProperty),
            .StringFormat = "RotateAngle = {0}"}
        sldRotateAngle.SetBinding(Slider.ValueProperty, b)
        tbRotateAngle.SetBinding(TextBlock.TextProperty, b)

        b = New Binding With {.Source = MyImage, .Path = New PropertyPath(Canvas.TopProperty),
            .StringFormat = "CanvasTop = {0}"}
        sldCanvasTop.SetBinding(Slider.ValueProperty, b)
        tbCanvasTop.SetBinding(TextBlock.TextProperty, b)

    End Sub

    Private Function GetTransform(t As Type) As Transform
        Dim tg As TransformGroup = MyImage.RenderTransform
        For Each c As Transform In tg.Children
            If c.GetType = t Then
                Return c
            End If
        Next
        Return Nothing
    End Function

    'イベント

    Private Sub btnEdge_Click(sender As Object, e As RoutedEventArgs)
        If RenderOptions.GetEdgeMode(MyImage) = EdgeMode.Aliased Then
            RenderOptions.SetEdgeMode(MyImage, EdgeMode.Unspecified)
        Else
            RenderOptions.SetEdgeMode(MyImage, EdgeMode.Aliased)
        End If
    End Sub

    Private Sub btnUseLayoutRounding_Click(sender As Object, e As RoutedEventArgs)
        MyImage.UseLayoutRounding = Not MyImage.UseLayoutRounding
    End Sub

    Private Sub btnSnapsToDevicePixels_Click(sender As Object, e As RoutedEventArgs)
        MyImage.SnapsToDevicePixels = Not MyImage.SnapsToDevicePixels
    End Sub

    Private Sub btnNearestNeighbor_Click(sender As Object, e As RoutedEventArgs)
        If RenderOptions.GetBitmapScalingMode(MyImage) = BitmapScalingMode.NearestNeighbor Then
            RenderOptions.SetBitmapScalingMode(MyImage, BitmapScalingMode.Unspecified)
        Else
            RenderOptions.SetBitmapScalingMode(MyImage, BitmapScalingMode.NearestNeighbor)
        End If
    End Sub

    Private Sub btnReset_Click(sender As Object, e As RoutedEventArgs)
        sldRotateAngle.Value = 0
        sldScaleX.Value = 1
        sldScaleY.Value = 1
        sldCanvasTop.Value = 50
        'Canvas.SetTop(MyImage, 50)
    End Sub
End Class
 
 
 
 
 
 
 
 
前回
EdgeModeでアンチエイリアスの有無を切り替え、WPF ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/14910458.html
 
 
イメージ 14
初代Pixtack紫陽花はWindowsFormアプリ
WindowsFormは回転表示でアンチエイリアスを無効にできなかった記憶がある
真ん中下段も微妙にアンチエイリアスがかかっている
 
イメージ 13
2代目Pixtack紫陽花はWPFアプリ
このときはEdgeModeの存在を知らなくて
ScalingModeの切り替えのみなので
回転表示のときは外側だけアンチエイリアスがかかっている
 
 
次のWPFの記事は1ヶ月後
WPFCanvasの中に画像として保存したい要素が回転や拡大など変形されていてもOKな方法 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/14977600.html
2018/01/09は8ヶ月後
WPF、dpiの変更とUseLayoutRoundingを指定して画像をくっきり表示 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15316739.html