コピペで使えるGUIカラーピッカー(Powershell)を作った【インストール不要】

日常の業務にて画面上のRGB色を取りたいという機会があったのですが、勝手にフリーソフト等をインストールできる環境ではなかったので、簡単に利用できるカラーピッカーGUIがあればなぁということで少し作ってみたという記事になります。

## 機能概要
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2964519/300af8e1-2a1f-1997-62df-5c0fc40996f3.png)

- F4キーでマウスカーソルにある色を取得可能(Hex, RGBの欄に値がセットされる)
- 取得したRGB,Hexをクリップボードにコピーする
- 履歴機能 (F4を押すと自動で履歴に追加される)
など

### 注意点
- それぞれDPIが異なるマルチディスプレイ環境だとカーソル位置から上手く色を取得できないかもしれません。
- Windows10, 11以外では動作させていません。

## 起動手順
1. 記事最下部のソースコード(250行程度)をコピー
2. テキストファイルに保存して実行する (拡張子は.ps1)

### 起動時のコンソールが邪魔な場合
powershellのGUIソフトは起動するとコンソールもおまけで表示されてしまうらしいため、それが嫌な場合は下記コマンド経由で起動すれば自動でコンソールが最小化されて起動されます。
```powershell
powershell -WindowStyle Minimized  -File ./color_picker.ps1
```
またこれを.batのファイルに保存しておけば、それ経由でアプリを起動することも可能ですし、またExecutionPolicy周りのエラーも回避できると思います。


## ソースコード(Powershell)
下記からコピペお願い致します。
ちなみにWinFormsではなくWPFを使用しています。

```powershell
Set-StrictMode -Version Latest

$assemblies = @(
    "PresentationFramework"
    "System.Windows.Forms"
    "System.Drawing"
)

Add-Type -AssemblyName $assemblies


function New-ControlFromXaml
{
    param (
        [xml]$xaml
    )
    $reader = [System.Xml.XmlNodeReader]::new($xaml)
    $window = [System.Windows.Markup.XamlReader]::Load($reader)
    return $window
}


$windowXaml = @"
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Color Picker"
  Width="500"
  Height="365"
  ResizeMode="NoResize"
  WindowStartupLocation="CenterScreen"
  Topmost="True"
  
  FontFamily="メイリオ">

  <Window.Resources>
    
  </Window.Resources>

  <DockPanel>
    
    <Label DockPanel.Dock="Bottom" Content="※F4キーでカーソル位置の色を取得"/>
    
    <Grid Margin="5">

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        
        <!-- 1行目 -->
        <Label
            Grid.Row="0"
            Grid.Column="0"
            Content="Hex"
            />
        <TextBox
            Name="txt_hex"
            Grid.Row="0"
            Grid.Column="1"
            Grid.ColumnSpan="2"
            Text="#FFFFFF"
            />
        <Button
            Name="btn_hex"
            Grid.Row="0"
            Grid.Column="3"
            Margin="10 0 0 0"
            Content="Copy"
            />

        <!-- 2行目 -->
        <Label
            Grid.Row="1"
            Grid.Column="0"
            Content="RGB"
            />
        <TextBox
            Name="txt_rgb"
            Grid.Row="1"
            Grid.Column="1"
            Grid.ColumnSpan="2"
            Text="RGB(255, 255, 255)"
            />
        <Button
            Name="btn_rgb"
            Grid.Row="1"
            Grid.Column="3"
            Margin="10 0 0 0"
            Content="Copy"
            />

        <!-- 3行目 -->
        <Label
            Grid.Row="2"
            Grid.Column="0"
            Content="Preview"
            />
        <Border
            Name="bdr_preview"
            Grid.Row="2"
            Grid.Column="1"
            BorderThickness="1"
            BorderBrush="Black"
            />

        <!-- 4行目 -->
        <Label
            Grid.Row="3"
            Grid.Column="0"
            Content="History"
            />
        <Button
            Name="btn_removeHistory"
            Grid.Row="3"
            Grid.Column="1"
            Content="Remove"
            Margin="0 5 0 5"
            />

        <!-- 5行目 -->
        <ListView
            Name="lsv_history"
            Grid.Row="4"
            Grid.Column="0"
            Grid.ColumnSpan="4">

            <ListView.View>
                <GridView>
                    <GridViewColumn Header="HEX" Width="80" DisplayMemberBinding="{Binding Hex}"/>
                    <GridViewColumn Header="RGB" Width="150" DisplayMemberBinding="{Binding Rgb}"/>
                    <GridViewColumn Header="Preview" Width="70">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <Border BorderThickness="1" BorderBrush="Black" Height="10" Width="10" Background="{Binding Hex}" />
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                    <GridViewColumn Header="Note" Width="160">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <TextBox Width="149" />
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                </GridView>
            </ListView.View>

        </ListView>

    </Grid>
  </DockPanel>

</Window>
"@


# RGB取得処理
function Get-ColorFromCursor() {
    $bitmap = New-Object System.Drawing.Bitmap(1, 1)
    $graphics = [System.Drawing.Graphics]::FromImage($bitmap)

    $dpiScaling = 1.0
    $positionX = [System.Windows.Forms.Cursor]::Position.X * $dpiScaling
    $positionY = [System.Windows.Forms.Cursor]::Position.Y * $dpiScaling

    # カーソルの位置のスクリーンの RGB 値を取得
    $graphics.CopyFromScreen($positionX, $positionY, 0, 0, $bitmap.Size)
    $pixel = $bitmap.GetPixel(0, 0)
    
    $result = @{
        "rgb" = "RGB({0}, {1}, {2})"  -f $pixel.R, $pixel.G, $pixel.B
        "hex" = "#{0:X2}{1:X2}{2:X2}" -f $pixel.R, $pixel.G, $pixel.B
    }

    return $result
}

# Main処理
if ($MyInvocation.InvocationName -ne ".")
{
    $window = New-ControlFromXaml $windowXaml
    
    $script:bdr_preview = $window.FindName("bdr_preview")
    $script:txt_rgb     = $window.FindName("txt_rgb")
    $script:btn_rgb     = $window.FindName("btn_rgb")
    $script:txt_hex     = $window.FindName("txt_hex")
    $script:btn_hex     = $window.FindName("btn_hex")
    $script:lsv_history = $window.FindName("lsv_history")
    $script:btn_removeHistory = $window.FindName("btn_removeHistory")

    $window.Add_KeyDown({
        $sender, $e = $this, $_

        if ($e.key -eq "F4")
        {
            $color = Get-ColorFromCursor
            $script:bdr_preview.Background = $color.hex

            $script:txt_rgb.Text = $color.rgb
            $script:txt_hex.Text = $color.hex

            $script:lsv_history.Items.Insert(0, [PSCustomObject]@{"Hex"=$color.hex; "Rgb"=$color.rgb;})
            # 先頭にスクロール
            $script:lsv_history.ScrollIntoView($script:lsv_history.Items[0])
        }
    })

    $script:btn_rgb.Add_Click({
        Set-Clipboard $script:txt_rgb.Text
    })

    $script:btn_hex.Add_Click({
        Set-Clipboard $script:txt_hex.Text
    })
    $script:btn_removeHistory.Add_Click({
        $lsv_history.Items.RemoveAt($lsv_history.SelectedIndex)
    })

    $script:lsv_history.Add_SelectionChanged({
        $color = $script:lsv_history.SelectedItem
        if ($color -eq $null) { return }
        $rgb   = $color.Rgb
        $hex   = $color.Hex
        $script:txt_rgb.Text = $rgb
        $script:txt_hex.Text = $hex
        $script:bdr_preview.Background = $color.Hex
    })

    $window.Add_ContentRendered({
        
    })

    $window.Add_Closing({
    
    })

    $window.ShowDialog() > $null

}
```

以上です