xlogI125’s blog

パソコン作業を効率化したい

PowerShell: クリップボードの白黒画像を白黒2値としてファイルに保存

メモ

  • Clipboard.GetImageメソッドで取得したImageから新しいBitmapを作成
  • 正の整数を8で割ったときの商を求めるため-shr 3を使用

使い捨てスクリプト

  • 普通に手作業でクリップボードの画像をペイントに貼り付けてモノクロビットマップとして保存したほうが早い
# PowerShell 5.1, Windows 11 (2023年11月頃)

$ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
$VerbosePreference = [System.Management.Automation.ActionPreference]::Continue

Set-StrictMode -Version Latest

Add-Type -AssemblyName System.Drawing
Add-Type -AssemblyName System.Windows.Forms

. {
  # クリップボードからImageを取得
  $bmpClip = [System.Drawing.Bitmap][System.Windows.Forms.Clipboard]::GetImage()

  if ($null -eq $bmpClip) {
    Write-Verbose '$null -eq $bmpClip'
    return
  }

  # クリップボードから取得したImageを用いて新しいBitmapを作成
  $bmpNew = [System.Drawing.Bitmap]::new($bmpClip)

  # PixelFormat が Format32bppArgb になってしまう様子なので確認
  if ($bmpNew.PixelFormat -ne [System.Drawing.Imaging.PixelFormat]::Format32bppArgb) {
    Write-Verbose "PixelFormat: $($bmpNew.PixelFormat)"
    Write-Verbose "PixelFormat が Format32bppArgb ではありません"
    $bmpClip.Dispose()
    $bmpNew.Dispose()
    return
  }

  # 白黒画像用にBitmapを作成
  $bmpBW = [System.Drawing.Bitmap]::new(
    $bmpNew.Width, $bmpNew.Height, 
    [System.Drawing.Imaging.PixelFormat]::Format1bppIndexed
  )

  # 解像度を設定
  $bmpBW.SetResolution($bmpNew.HorizontalResolution, $bmpNew.VerticalResolution)

  # LockBits
  $dataNew = [System.Drawing.Imaging.BitmapData]$bmpNew.LockBits(
    [System.Drawing.Rectangle]::new(0, 0, $bmpNew.Width, $bmpNew.Height), 
    [System.Drawing.Imaging.ImageLockMode]::ReadOnly, $bmpNew.PixelFormat
  )

  $dataBW = [System.Drawing.Imaging.BitmapData]$bmpBW.LockBits(
    [System.Drawing.Rectangle]::new(0, 0, $bmpBW.Width, $bmpBW.Height), 
    [System.Drawing.Imaging.ImageLockMode]::WriteOnly, $bmpBW.PixelFormat
  )

  # ストライドは正の値を仮定
  $arrNewByte = [byte[]]::new($dataNew.Stride * $dataNew.Height)

  # ピクセルデータを配列にコピー
  [System.Runtime.InteropServices.Marshal]::Copy($dataNew.Scan0, $arrNewByte, 0, $arrNewByte.Length)

  # 白黒画像用のBitArray
  $arrBWBit = [System.Collections.BitArray]::new(8 * $dataBW.Stride * $dataBW.Height)

  for ($y = 0; $y -lt $dataNew.Height; $y++) {
    $numBytesNewY = $dataNew.Stride * $y
    $numBitsBWY = 8 * $dataBW.Stride * $y

    for ($x = 0; $x -lt $dataNew.Width; $x++) {
      # Blue, Green, Red, Alpha
      $numBytesNew = $numBytesNewY + 4 * $x

      $b = $arrNewByte[$numBytesNew + 0]
      $g = $arrNewByte[$numBytesNew + 1]
      $r = $arrNewByte[$numBytesNew + 2]
      $a = $arrNewByte[$numBytesNew + 3]

      if (($x -eq 0) -and ($y -eq 0)) {
        Write-Host "$("{0:000}" -f $x), $("{0:000}" -f $y) : " -ForegroundColor Yellow -NoNewline
        Write-Host "$a, " -ForegroundColor Gray  -NoNewline
        Write-Host "$r, " -ForegroundColor Red   -NoNewline
        Write-Host "$g, " -ForegroundColor Green -NoNewline
        Write-Host "$b"   -ForegroundColor Blue
      }

      # 正の整数を8で割ったときの商を求めるため -shr を使用
      #  5 -shr 3   #=> 0
      # 15 -shr 3   #=> 1
      # [int]( 5/8) #=> 1
      # [int](15/8) #=> 2
      $numBitsBW = $numBitsBWY + 8 * ($x -shr 3) + (7 - ($x % 8))

      # とりあえず (R, G, B) = (255, 255, 255) の場合のみ白色(true)としておく
      $arrBWBit[$numBitsBW] = ($r -eq 255) -and ($g -eq 255) -and ($b -eq 255)
    }
  }

  $arrBWByte = [byte[]]::new($dataBW.Stride * $dataBW.Height)

  # BitArray を byte[] にコピー
  $arrBWBit.CopyTo($arrBWByte, 0)

  # byte[] を Scan0 にコピー
  [System.Runtime.InteropServices.Marshal]::Copy($arrBWByte, 0, $dataBW.Scan0, $arrBWByte.Length)

  $arrBWByte = $null

  # UnlockBits
  $bmpNew.UnlockBits($dataNew)

  $bmpBW.UnlockBits($dataBW)

  $bmps = [System.Drawing.Bitmap[]]@($bmpClip, $bmpNew, $bmpBW)

  # プロパティを表示
  $bmps | Select-Object -Property @(
    @{Name = "W"; Expr = {$_.Width}}
    @{Name = "H"; Expr = {$_.Height}}
    @{Name = "DpiX"; Expr = {$_.HorizontalResolution}}
    @{Name = "DpiY"; Expr = {$_.VerticalResolution}}
    "PixelFormat"
    @{Name = "ColorPaletteEntries"; Expr = {$_.Palette.Entries}}
  ) | Format-Table -AutoSize

  $bmpBW.Save("${env:USERPROFILE}\Desktop\test.png", [System.Drawing.Imaging.ImageFormat]::Png)

  $bmps.ForEach({$_.Dispose()})
}