You can indeed use LockBits()
to access the bitmap data directly:
using namespace System.Drawing
using namespace System.Runtime.InteropServices
# Load PNG file
$pathToFile = "D:\image.png"
$bmp = [Bitmap]::FromFile($pathToFile)
# Lock all the bits (from top left to bottom right)
$bounds = [Rectangle]::new([Point]::Empty, $bmp.Size)
$data = $bmp.LockBits($bounds, 'ReadOnly', 'Format32bppArgb')
# Scan0 is a pointer to the raw bitmap in local memory
# Let's read the ARGB value at 0,0
$px00 = [Marshal]::ReadInt32($data.Scan0, 0)
$px00color = [Color]::FromArgb($px00)
Beware that since Int32 is 32 bits = 4 bytes wide, the next ARGB value is always at the current offset + 4. To read the pixel value at 0,1 for example:
$px01 = [Marshal]::ReadInt32($data.Scan0, 4)
To enumerate ALL pixel values, you could therefore do:
$pixelLength = $bmp.Width * $bmp.Height
$allPixels = for($offset = 0; $offset -lt $pixelLength; $offset++)
{
[Color]::FromArbg([Marshal]::ReadInt32($data.Scan0, $offset * 4))
}
# don't forget to clean up original bitmap
$bmp.UnlockBits($data)
$bmp.Dispose()
If you're working with large images, this will still become slow due to all the pixel data accumulated, so you might want to optimize your pixel collection process specifically for your use case.
Let's say we wanted to identify the top 25 colors in an image - in that case we don't need to collect/store each individual pixel value, only one for each distinct color. For this, a hashtable (or any other dictionary-like data type) would be way more efficient:
$pathToFile = "D:\image.png"
$top = 25
$bmp = [System.Drawing.Bitmap]::FromFile($file.FullName)
# Create hashtable to keep track of values seen
$counts = @{}
try {
# Lock all bits
$data = $bmp.LockBits([System.Drawing.Rectangle]::new([System.Drawing.Point]::Empty, $bmp.Size), [System.Drawing.Imaging.ImageLockMode]::ReadOnly, [System.Drawing.Imaging.PixelFormat]::Format32bppArgb)
try {
$length = $bmp.Width * $bmp.Height
for($i = 0; $i -lt $length; $i++)
{
$argb = [System.Runtime.InteropServices.Marshal]::ReadInt32($data.Scan0, $i * 4)
# Increase counter per found ARGB value
$counts[$argb]++
}
}
finally{
# Clean up our read-only handle
$bmp.UnlockBits($data)
}
}
finally{
# Clean up source bmp
$bmp.Dispose()
}
# Pick the top 25 values and convert those to [Color]
$topArgbValues = $counts.GetEnumerator() |Sort Value -Descending |Select -First $top
$topArgbValues |Select @{Name='Color';Expression={[System.Drawing.Color]::FromArgb($_.Key)}},@{Name='Count';Expression={$_.Value}}