xlogI125’s blog

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

PowerShell: Bitmap.LockBits (Format1bppIndexed)

メモ

  • ピクセル形式にFormat1bppIndexedを用いる場合におけるBitmap.LockBitsメソッドの使い方メモ

使い捨てスクリプト

書き込み

  • 横32×縦8の値を手入力してPNG形式で保存
# PowerShell 5.1, Windows 11 (2023年7月頃)

Set-StrictMode -Version Latest

Add-Type -AssemblyName System.Drawing

$bmp = [Drawing.Bitmap]::new(
  13,  # 幅(ピクセル単位)
  8,   # 高さ(ピクセル単位)
  [Drawing.Imaging.PixelFormat]::Format1bppIndexed  # ピクセル形式
)

# Bitmap.LockBits(Rectangle, ImageLockMode, PixelFormat)
$bmpData = $bmp.LockBits(
  [Drawing.Rectangle]::new(0, 0, $bmp.Width, $bmp.Height), 
  [Drawing.Imaging.ImageLockMode]::WriteOnly, 
  $bmp.PixelFormat
)

$bmpData | Format-List -Property * | Out-String -Width 80 | Write-Verbose -Verbose

if ($bmpData.Stride -lt 0) {
  Write-Error "Stride が負の値です" -ErrorAction Stop
}

$p0 = [IntPtr]$bmpData.Scan0

$w0 = 1 -shl 7    # 0x80 ... 10進数 128 ... 2進数 10000000
$w1 = 1 -shl 6    # 0x40 ... 10進数  64 ... 2進数  1000000
$w2 = 1 -shl 5    # 0x20 ... 10進数  32 ... 2進数   100000
$w3 = 1 -shl 4    # 0x10 ... 10進数  16 ... 2進数    10000
$w4 = 1 -shl 3    # 0x08 ... 10進数   8 ... 2進数     1000
$w5 = 1 -shl 2    # 0x04 ... 10進数   4 ... 2進数      100
$w6 = 1 -shl 1    # 0x02 ... 10進数   2 ... 2進数       10
$w7 = 1 -shl 0    # 0x01 ... 10進数   1 ... 2進数        1

# ストライド幅は4byte(32bit)単位なので、今回は32bit×8行を手入力する。
$L0B0 = $w0 + $w1 + $w2 + $w3 + $w4 + $w5 + $w6 + $w7; $L0B1 = $w0 + $w1 + $w2 + $w3 + $w4; $L0B2 = 0x00; $L0B3 = 0x00
$L1B0 =       $w1 +       $w3 +       $w5 +       $w7; $L1B1 =       $w1 +       $w3      ; $L1B2 = 0x00; $L1B3 = 0x00
$L2B0 = $w0 +       $w2 +       $w4 +       $w6      ; $L2B1 = $w0 +       $w2 +       $w4; $L2B2 = 0x00; $L2B3 = 0x00
$L3B0 = $w0 + $w1       + $w3 +       $w5 +       $w7; $L3B1 =       $w1       + $w3 + $w4; $L3B2 = 0x00; $L3B3 = 0x00
$L4B0 = $w0 + $w1 + $w2 +       $w4 +     + $w6      ; $L4B1 = $w0 +       $w2 + $w3 + $w4; $L4B2 = 0x00; $L4B3 = 0x00
$L5B0 = $w0 + $w1 + $w2 + $w3 +       $w5 +       $w7; $L5B1 =       $w1 + $w2 + $w3 + $w4; $L5B2 = 0x00; $L5B3 = 0x00
$L6B0 = $w0 + $w1 + $w2 + $w3 + $w4 +       $w6      ; $L6B1 = $w0 + $w1 + $w2 + $w3 + $w4; $L6B2 = 0x00; $L6B3 = 0x00
$L7B0 = $w0 + $w1 + $w2 + $w3 + $w4 + $w5 +       $w7; $L7B1 = $w0 + $w1 + $w2 + $w3 + $w4; $L7B2 = 0x00; $L7B3 = 0x00

$val = [Byte[]]@(
  $L0B0, $L0B1, $L0B2, $L0B3, 
  $L1B0, $L1B1, $L1B2, $L1B3, 
  $L2B0, $L2B1, $L2B2, $L2B3, 
  $L3B0, $L3B1, $L3B2, $L3B3, 
  $L4B0, $L4B1, $L4B2, $L4B3, 
  $L5B0, $L5B1, $L5B2, $L5B3, 
  $L6B0, $L6B1, $L6B2, $L6B3, 
  $L7B0, $L7B1, $L7B2, $L7B3
)

# Marshal.Copy(Byte[], Int32, IntPtr, Int32)
[Runtime.InteropServices.Marshal]::Copy($val, 0, $p0, $val.Length)

# Bitmap.UnlockBits(BitmapData)
$bmp.UnlockBits($bmpData)

$fs = [IO.File]::Open(
  "${env:USERPROFILE}\Desktop\test.png", 
  [IO.FileMode]::Create, 
  [IO.FileAccess]::Write, 
  [IO.FileShare]::Write
)

$bmp.Save($fs, [Drawing.Imaging.ImageFormat]::Png)

$fs.Dispose()

$bmp.Dispose()

読み込み

# PowerShell 5.1, Windows 11 (2023年7月頃)

Set-StrictMode -Version Latest

Add-Type -AssemblyName System.Drawing

# "test.bmp"の ビットの深さ は1であることを前提
$fs = [IO.File]::Open(
  "${env:USERPROFILE}\Desktop\test.bmp", 
  [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::Read
)

# Bitmapコンストラクタの動作確認
& {
  $imgTmp = [Drawing.Image]::FromStream($fs, $true, $true)
  $bmpTmp = [Drawing.Bitmap]::new($imgTmp)
  Write-Verbose "imgTmp.PixelFormat: $($imgTmp.PixelFormat)" -Verbose
  #=> Format1bppIndexed
  Write-Verbose "bmpTmp.PixelFormat: $($bmpTmp.PixelFormat)" -Verbose
  #=> Format32bppArgb
  $bmpTmp.Dispose()
  $imgTmp.Dispose()
}

# とりあえずコンストラクタは Bitmap(Stream) を使用
$bmp = [Drawing.Bitmap]::new($fs)

if ($bmp.PixelFormat -ne [Drawing.Imaging.PixelFormat]::Format1bppIndexed) {
  Write-Error "PixelFormat が Format1bppIndexed ではありません" -ErrorAction Stop
}

# Bitmap.LockBits(Rectangle, ImageLockMode, PixelFormat)
$bmpData = $bmp.LockBits(
  [Drawing.Rectangle]::new(0, 0, $bmp.Width, $bmp.Height), 
  [Drawing.Imaging.ImageLockMode]::ReadOnly, 
  $bmp.PixelFormat
)

if ($bmpData.Stride -lt 0) {
  Write-Error "Stride が負の値です" -ErrorAction Stop
}

$p0 = [IntPtr]$bmpData.Scan0
$arr = [Byte[]]::new($bmpData.Stride * $bmpData.Height)

# Marshal.Copy(IntPtr, Byte[], Int32, Int32)
[Runtime.InteropServices.Marshal]::Copy($p0, $arr, 0, $arr.Length)

$lines = [Byte[][]]::new($bmpData.Height, $bmpData.Stride)

for ($y = 0; $y -lt $bmpData.Height; $y++) {
  # Array.Copy(Array, Int32, Array, Int32, Int32)
  [Array]::Copy($arr, $bmpData.Stride * $y, $lines[$y], 0, $bmpData.Stride)
}

$p0 = $null
$arr = $null

# Bitmap.UnlockBits(BitmapData)
$bmp.UnlockBits($bmpData)

$bmp.Dispose()
$fs.Dispose()

$psObjs = [PSObject[]]::new($lines.Length)
for ($y = 0; $y -lt $psObjs.Length; $y++) {
  $psObjs[$y] = [PSObject]::new()
}

for ($y = 0; $y -lt $lines.Length; $y++) {
  for ($byte = 0; $byte -lt $lines[$y].Length; $byte++) {
    for($bit = 7; $bit -ge 0; $bit--) {
      $params = @{
        InputObject = $psObjs[$y]
        MemberType = "NoteProperty"
        Name = (8 * $byte + 7 - $bit).ToString("000") + "px"
        Value = ($lines[$y][$byte] -shr $bit) -band 0x01
      }
      Add-Member @params
    }
  }
}

$psObjs | ConvertTo-Csv -NoTypeInformation | Set-Clipboard