I am trying to convert a PNG to a scaled bitmap. The end goal is to scale the image maintaining aspect ratio to be a height of 165 pixels. The following code works fine, and I get the image I expect as a bitmap, although it only reduces the resolution by half and does not calculate what the scale should be:
# Current test file is 256x256
$InFile = '.\someImage.png'
# Read in the initial image as FileStream
[System.IO.FileStream]$imageStream = [System.IO.FileStream]::new(
"$(Resolve-Path $InFile)",
[System.IO.FileMode]::Open,
[System.IO.FileAccess]::Read,
[System.IO.FileShare]::Read
)
# Read image FileStream into BitmapSource
$bitmapSource = [System.Windows.Media.Imaging.PngBitmapDecoder]::new(
$imageStream,
[System.Windows.Media.Imaging.BitmapCreateOptions]::PreservePixelFormat,
[System.Windows.Media.Imaging.BitmapCacheOption]::Default
).Frames[0]
$rotatedBitmap = [System.Windows.Media.Imaging.TransformedBitmap]::new(
$bitmapSource,
[System.Windows.Media.RotateTransform]::new(-90)
)
# Scale down the image so it isn't too large
$scale = .5
$scaledBitmap = [System.Windows.Media.Imaging.TransformedBitmap]::new(
$rotatedBitmap,
[System.Windows.Media.ScaleTransform]::new($scale, $scale)
)
# Create new bitmap from rotated/scaled bitmap using BGRA32 pixel format
$convertedBitmap = [System.Windows.Media.Imaging.FormatConvertedBitmap]::new()
$convertedBitmap.BeginInit()
$convertedBitmap.Source = $scaledBitmap
$convertedBitmap.DestinationFormat = [System.Windows.Media.PixelFormats]::Bgra32
$convertedBitmap.EndInit()
# Prepare transformed bitmap for writing via FileStream
$pixels = [byte[]]::new(( $convertedBitmap.PixelWidth * $convertedBitmap.PixelHeight * 4 ))
$stride = $convertedBitmap.PixelWidth * 4 + ( $convertedBitmap.PixelWidth % 4 )
$convertedBitmap.CopyPixels($pixels, $stride, 0)
However, if I use a different value for the scale, I get an error on the final line of the block. For example, changing the scale like so:
# Change the $scale = value in the previous code block to this
# for capping the height at 165 pixels and execute to reproduce
$scale = 165 / $rotatedBitmap.PixelHeight
Causes $convertedBitmap.CopyPixels
to throw an error:
Exception calling "CopyPixels" with "3" argument(s): "The parameter value cannot be less than '109064'.
Parameter name: buffer"
At C:\Users\bender\OneDrive\Documents\src\personal\MyProject\Test-Function.ps1:89 char:9
+ $convertedBitmap.CopyPixels($pixels, $stride, 0)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ArgumentOutOfRangeException
Why does my code break when I change the scaling? My initial research suggests either the $stride
is wrong or the $pixels
buffer is incorrectly sized, but I'm not sure how that is the case since I calculate both from the scaled bitmap, and it works with other scaling values (or omitting scaling altogether).
Just remove the term + ( $convertedBitmap.PixelWidth % 4 )
. As you have converted the bitmap to BGRA32
format, you only need to multiply the pixel width by 4 to get a stride (width in bytes) that is a multiple of 4, as required:
$stride = $convertedBitmap.PixelWidth * 4
Just for completeness, if you had used the input bitmap as-is, so it could be other bit depths than 32, the formula should be:
$stride = (((($bitmap.PixelWidth * $bitmap.Format.BitsPerPixel) + 31) -band -bnot 31) -shr 3)
I haven't tested it though, I just ported the formula from this doc to powershell.