1

I found a C# method in this SO answer to detect whether or not an image has transparency. I am trying to convert the method to PowerShell.

Unfortunately I can't seem to get it to work. Everything seems to work fine up until the Marshal.Copy part, and all the values are filled properly, except Bytes array which is full of zeroes even after Marshal.Copy.

Following is the original code in C#:

public bool IsAlphaBitmap(ref System.Drawing.Imaging.BitmapData BmpData)
{
    byte[] Bytes = new byte[BmpData.Height * BmpData.Stride];
    Marshal.Copy(BmpData.Scan0, Bytes, 0, Bytes.Length);
    for (p = 3; p < Bytes.Length; p += 4) {
        if (Bytes[p] != 255) return true;
    }
    return false;
}

And this is the PowerShell version I have so far (that doesn't work):

function IsAlphaBitMap([string]$imagepath)
{
    $BitMap  = New-Object System.Drawing.BitMap($imagepath)
    $Rect    = New-Object System.Drawing.Rectangle(0, 0, $BitMap.Width, $BitMap.Height)
    $BmpData = [System.Drawing.Imaging.BitmapData]$BitMap.LockBits($Rect, [System.Drawing.Imaging.ImageLockMode]::ReadWrite, $BitMap.PixelFormat)

    $Bytes   = New-Object byte[] ($BmpData.Height * $BmpData.Stride)
    [System.Runtime.InteropServices.Marshal]::Copy($BmpData.Scan0, $Bytes, 0, $Bytes.Length)
    for($p=3 ; $p -lt $Bytes.Length ; $p+=4)
    {
        if ($Bytes[$p] -ne 255) { write-host $Bytes[$p] } # { return $true }
    }
    return $false
}

I did use a write-host in the loop rather than the return value so I that can read the output of each iteration, which is always zero. Can anyone see where I went wrong?

Community
  • 1
  • 1
Bighead
  • 75
  • 6
  • You may consider adding the output from the `write-host`, even if that output is rows of zeroes. Also, did you heed the warning in one of the comments where it says "This only works for 32 bpp images"? If you did, you should say that you ran your tests using such images. – Sabuncu Feb 10 '17 at 08:11
  • Sorry I didn't make that clear. You are correct, I only intended to run this on 32-Bit RGBA PNG images. For PNG type 0, 2, and 3 I check for the tRNS chunk, and type 4 I check with ImageMagick (it's slow!) but rare in my case so that's okay. I was hoping this would be faster than using ImageMagick but I have yet to get it to work. – Bighead Feb 10 '17 at 16:47

2 Answers2

2

Your code works for me as is (PS5.1 on Win7), but it has issues. Some pictures don't have an alpha channel so each pixel consists of just the image bytes, no alpha. The number of bytes is also different depending on the format: less than one, 1, 2, 3, 4, 6, 8 bytes.

Check PixelFormat as a bit mask flag:

$BitMap  = [Drawing.BitMap]$imagepath
if ($BitMap.PixelFormat.value__ -band [Drawing.Imaging.PixelFormat]::Alpha -eq 0) {
    return $false
}

And use the actual pixel length:

$pixelBytes = [Drawing.Image]::GetPixelFormatSize($bitmap.PixelFormat) -shr 3
if (!$pixelBytes) {
    Write-Warning 'Sorry, 2lazy2support paletted images with pixels less than 8 bits'
    return $null
}

$len = $Bytes.Length
for ($p = $pixelBytes - 1; $p -lt $len; $p += $pixelBytes) {
    if ($Bytes[$p] -ne 255) {
        return $true
    }
}
wOxxOm
  • 65,848
  • 11
  • 132
  • 136
  • Thank you for the suggestion, this is extremely useful and extended my original goal for this function. – Bighead Feb 10 '17 at 17:32
0

Thank you for the suggestions, now I can use it for other images as well! And it finally works now somehow! Here is the full working function + wOxxOm's suggestions in case anyone stumbles upon it in a Google search. I know I ended up here many times over the years when searching for answers, this really is a great site with great people.

function IsAlphaBitMap([string]$imagepath)
{
    $BitMap = [Drawing.BitMap]$imagepath
    if (($BitMap.PixelFormat.value__ -band [Drawing.Imaging.PixelFormat]::Alpha) -eq 0)
    {   
        return $false
    }
    $pixelBytes = [Drawing.Image]::GetPixelFormatSize($BitMap.PixelFormat) -shr 3
    if (!$pixelBytes)
    {
        Write-Warning 'Sorry, 2lazy2support paletted images with pixels less than 8 bits'
        return $null
    }
    $Rect    = New-Object System.Drawing.Rectangle(0, 0, $BitMap.Width, $BitMap.Height)
    $BmpData = [Drawing.Imaging.BitmapData]$BitMap.LockBits($Rect, [System.Drawing.Imaging.ImageLockMode]::ReadWrite, $BitMap.PixelFormat)
    $Bytes   = New-Object byte[] ($BmpData.Height * $BmpData.Stride)
    [System.Runtime.InteropServices.Marshal]::Copy($BmpData.Scan0, $Bytes, 0, $Bytes.Length)
    $BitMap.Dispose()

    $len = $Bytes.Length
    for($p = ($pixelBytes - 1) ; $p -lt $len ; $p += $pixelBytes)
    {
        if ($Bytes[$p] -ne 255)
        {
            return $true
        }
    }
    return $false
}

Edited because I forgot to dispose the bitmap which caused issues for me later.

Bighead
  • 75
  • 6