0
Add-Type -AssemblyName System.Windows.Forms 

$pic = [Drawing.Bitmap]::new("D:\image.png")
$gfx = [Drawing.Graphics]::FromImage($pic)

$pic.GetPixel(1,1).name

How to get color of each pixel, using .NET methods? Taking each one by one is really slow. I thinking, taht there is a properly way to do it fast.

I heard, that LockBits maybe can help here, but if it is, I dont know how to write this...

Danny
  • 410
  • 2
  • 8
  • GetPixel slow because it locks and unlocks each pixel in turn, and locking a bunch of pixels will be quicker but you do really need to specify better what the outputof this will realistically be. In a 1920x1080 image, printing 2 million colour names (?) to the console isn't very workable. What will you do with all these colour values? – Caius Jard Dec 22 '20 at 21:45
  • Had it been C# code - you could parallel process pixels. However, this seems more of a decision problem. Do you really need to see each pixel and note color? Can you not sample some from each row? What is the objective actually? – Prateek Shrivastava Dec 22 '20 at 21:46
  • https://stackoverflow.com/questions/19586524/get-all-pixel-information-of-an-image-efficiently – Caius Jard Dec 22 '20 at 21:46
  • @Prateek what, powershell isn't c#? :) – Caius Jard Dec 22 '20 at 21:47
  • "What will you do with all these colour values?" - I want to search color map/matrix of another image on source image. "Had it been C# code - you could parallel process pixels" - powershell is a .net like a c#, so powershell have parallel ability. " https://stackoverflow.com/questions/19586524/get-all-pixel-information-of-an-image-efficiently " can u help me to translate this in powershell? – Danny Dec 22 '20 at 21:53
  • 1
    Stack Overflow is a Q&A site, not a code translation service. Try to translate the code yourself first, then come to us when you are stuck, making sure to show us what you have tried and create a [mre]. – gunr2171 Dec 22 '20 at 21:59
  • @CaiusJard, or there is a some another way of searching some target image on a source image? – Danny Dec 22 '20 at 22:26
  • 1
    Sure, like https://www.codeproject.com/Articles/38619/Finding-a-Bitmap-contained-inside-another-Bitmap or https://codereview.stackexchange.com/questions/138011/find-a-bitmap-within-another-bitmap/138012 – Caius Jard Dec 23 '20 at 00:07

1 Answers1

2

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}}
Theo
  • 57,719
  • 8
  • 24
  • 41
Mathias R. Jessen
  • 157,619
  • 12
  • 148
  • 206