午後わてんのブログ

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

WPFとVB.NET、表示した画像をクリックした場所の色の取得はややこしい

WPFで画像をクリックした場所の色を取得
 
イメージ 2

f:id:gogowaten:20191025113034p:plain

 

f:id:gogowaten:20191025113044p:plain

Class MainWindow
    Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded

        Dim b As BitmapImage
        Dim path As Uri
        path = New Uri("D:\ブログ用\テスト用画像\TransparentRect.png")
        'path = New Uri("D:\ブログ用\テスト用画像\TransparentRect.jpg")
        'path = New Uri("D:\ブログ用\テスト用画像\TransparentRect.bmp")
        'path = New Uri("D:\ブログ用\テスト用画像\TransparentRect.gif")
        'path = New Uri("D:\ブログ用\テスト用画像\TransparentRect.tiff")
        b = New BitmapImage(path)
        image1.Source = b
        image1.Stretch = Stretch.None
    End Sub

    Private Sub image1_MouseLeftButtonDown(sender As Object, e As MouseButtonEventArgs) Handles image1.MouseLeftButtonDown
        '        [VB.NET][WPF] マウスクリックした箇所のスクリーン座標(絶対座標)を取得する | オールトの雲
        'http://ooltcloud.expressweb.jp/201312/article_08004931.html

        Dim clickPoint As Point = e.GetPosition(image1)
        textblock1.Text = $"Locate={clickPoint.ToString}"

        Dim x As Integer = clickPoint.X
        Dim y As Integer = clickPoint.Y
        Dim b As BitmapSource = image1.Source
        Dim wb As Integer = b.PixelWidth
        Dim hb As Integer = b.PixelHeight
        '画像の端ギリギリをクリックすると画像サイズより大きな値になることがあるのでその時は-1する
        If x >= wb Then x = wb - 1
        If y >= hb Then y = hb - 1
        'Dim col As Color = GetPixel(x, y)
        'Dim col As Color = neko(x, y)
        Dim col As Color = neko2(x, y)
        rectangle1.Fill = New SolidColorBrush(col)
        tbARGB.Text = $"ARGB={col.ToString}"
    End Sub

    '    wpf - Finding specific pixel colors of a BitmapImage - Stack Overflow
    'http://stackoverflow.com/questions/1176910/finding-specific-pixel-colors-of-a-bitmapimage
    '1x1のBitmapを切り出してPixelFormatをBgra32に変換して
    'CopyPixelsでピクセルデータの配列にしてRGBAを取り出す
    'x,yはクリックした位置
    Private Function neko2(x As Integer, y As Integer) As Color
        Dim bmp As BitmapSource = image1.Source '表示画像取得
        Dim cb As New CroppedBitmap(bmp, New Int32Rect(x, y, 1, 1))
        Dim fcb As New FormatConvertedBitmap(cb, PixelFormats.Bgra32, Nothing, 0)
        Dim pixels(3) As Byte 'コピー先の場所作成
        'cb.CopyPixels(pixels, 4, 0)
        fcb.CopyPixels(pixels, 4, 0)
        Dim c As Color ' = Colors.White
        c = Color.FromArgb(pixels(3), pixels(2), pixels(1), pixels(0))
        Return c
    End Function
End Class
余計なコメントや名前が変だけどこんな感じ
 
 
Windowsフォームアプリの時は簡単に取得できるGetPixelメソッドが
用意されていたんだけど
WPFには無いみたいで

stackoverflow.com

ここを参考にして(またしーしゃーぷか!しかも英語)
Private Function GetPixelColor(x As Integer, y As Integer, bmp as BitmapSource) As Color
    Dim cb As New CroppedBitmap(bmp, New Int32Rect(x, y, 1, 1))
    Dim fcb As New FormatConvertedBitmap(cb, PixelFormats.Bgra32, Nothing, 0)
    Dim pixels(3) As Byte
    fcb.CopyPixels(pixels, 4, 0)
    Dim c As Color
    c = Color.FromArgb(pixels(3), pixels(2), pixels(1), pixels(0))
    Return c
End Function
xとyがクリックした位置、bmpがクリックした画像
CroppedBitmapでbmpから1x1の大きさの画像を取り出す
これがクリックされたPixelになるので
あとはCopyPixelsで色の値を取り出すだけなんだけど
その前にFormatConvertedBitmapでピクセルフォーマットをBgra32に変換している
これは

f:id:gogowaten:20191025113309p:plain

CopyPixelsの引数に必要なBitmapのstrideをいくつにすればいいのかわかりやすくなるから
 
Strideは
画像のピクセルフォーマットと画像の横幅というか横に並んだピクセルの数
このふたつが関係しているみたいで
ピクセルフォーマットがBgra32で横幅100の画像の時Strideは
4*100=400
になる
今回はクリックした位置の1x1の画像だから横幅1で4*1=4
つまりBgra32なら横幅x4でいいみたい
 
どんな画像でもピクセルフォーマットがBgra32ならいいけど
いろいろあってそれによってx4の部分が変わるらしいけどよくわかんないので
FormatConvertedBitmapっていう便利なのがあったのでBgra32に統一した
なので画像によっては違う色になるかも?
適当な画像で試した限りでは問題なかった
拡張子でいったらpng,jpeg,bmp,gif,tiffこの辺りは大丈夫だったけど
同じ形式でもいろいろなピクセルフォーマットはあるみたいでよくわからん
 
コピーした値
イメージ 5
BGRAそれぞれの4つの値が配列にコピーされたところ
 
 
 
画像を表示させているimage1のMouseLeftDownイベントに
Private Sub image1_MouseLeftDown(sender As Object, e As MouseButtonEventArgs) Handles image1.MouseLeftButtonDown
    Dim clickPoint As Point = e.GetPosition(image1)
    textblock1.Text = $"Locate={clickPoint.ToString}"

    Dim x As Integer = clickPoint.X
    Dim y As Integer = clickPoint.Y
    Dim b As BitmapSource = image1.Source
    Dim wb As Integer = b.PixelWidth
    Dim hb As Integer = b.PixelHeight
   '画像の端ギリギリをクリックすると画像サイズより大きな値になることがあるのでその時は-1する
    If x >= wb Then x = wb - 1
    If y >= hb Then y = hb - 1
    Dim col As Color = GetPixelColor(x, y, b)
    rectangle1.Fill = New SolidColorBrush(col)
    tbARGB.Text = $"ARGB={col.ToString}"
End Sub
クリックした場所は
マウスイベントのGetPositionで取得できる
けどなんかおかしくて
100x100のピクセルサイズの画像なのに
端ギリギリをクリックした時に101とかが返ってくる時があるので
サイズを超えていた時は-1してからGetPixelColorに渡している
 
 
イメージ 6
マウスカーソルの形を変えて色取得モードに
カーソル下の色を常に表示
画像クリックで色確定して色取得モード終了
途中でやめるときはもう一度色取得ボタン押す
 
WPFではクリックした場所の色取得がこんなに面倒だとは思わなかった
なんでもかんでもWPFのほうがいいってわけでもないんだなあと
 
 
複数画像をまとめるのが目的のアプリだし保存するときは画像形式によって
ある程度ピクセルフォーマットも決まるみたいだから
画像読み込み時にピクセルフォーマットをBgra32に変換するようにした
これなら色取得の度にピクセルフォーマットを変換する手間が省ける
 
 
Pixtack2nd_20160303.zipダウンロード
これで色取得はできたと思ったけど終わってなかった
この記事の続きは

gogowaten.hatenablog.com

 
WPFVB.NETで表示した画像をクリックした場所の色を取得はややこしい(後編) ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/13955791.html
 
3年後の
 
WPF?画面上のどこでもマウスカーソル下の色を取得 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15890527.html
アプリのウィンドウ外でも色取得