Resize image and preserve ratio with Powershell

My recently created company website Keluro has two blogs: one in french and one in english. The main blog pages are a preview list of the existing posts. I gave the possibility to the writer to put a thumbnail image in the preview. It’s a simply <img /> tag where a css class is responsible for the resizing and to display the image in a 194px X 194px box while keeping the original aspect ratio. Most of the time this preview is a reduction of an image that is displayed in the blog post. Everything was fine until I found out that the these blog pages did not received a good mark while inspecting them with PageSpeedInsights . It basically says that the thumbnails were not optimized enough… For SEO reasons I want these blog pages to load quickly so I needed to resize these images once for all even if it has to duplicate the image.

Resizing and keeping aspect ratio

Resizing two pictures with landscape and portrait aspect ratio to make them fill a given canvas.

I think that most of us already had to do such kind of image resizing task. You can use many available software to do that: Paint, Office Picture Manager, Gimp, Inkscape etc. However, when it comes to manipulate many pictures, it could be really useful to use a script. Let me share with you this Powershell script that you can use to resize your .jpg pictures. Note that there is also a quality parameter (from 1 to 100) that you can use if you need to compress more the image.

Param ( [Parameter(Mandatory=$True)] [ValidateNotNull()] $imageSource,
[Parameter(Mandatory=$True)] [ValidateNotNull()] $imageTarget,
[Parameter(Mandatory=$true)][ValidateNotNull()] $quality )

if (!(Test-Path $imageSource)){throw( "Cannot find the source image")}
if(!([System.IO.Path]::IsPathRooted($imageSource))){throw("please enter a full path for your source path")}
if(!([System.IO.Path]::IsPathRooted($imageTarget))){throw("please enter a full path for your target path";)}
if ($quality -lt 0 -or $quality -gt 100){throw( "quality must be between 0 and 100.")}

[void][System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
$bmp = [System.Drawing.Image]::FromFile($imageSource)

#hardcoded canvas size...
$canvasWidth = 194.0
$canvasHeight = 194.0

#Encoder parameter for image quality
$myEncoder = [System.Drawing.Imaging.Encoder]::Quality
$encoderParams = New-Object System.Drawing.Imaging.EncoderParameters(1)
$encoderParams.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter($myEncoder, $quality)
# get codec
$myImageCodecInfo = [System.Drawing.Imaging.ImageCodecInfo]::GetImageEncoders()|where {$_.MimeType -eq 'image/jpeg'}

#compute the final ratio to use
$ratioX = $canvasWidth / $bmp.Width;
$ratioY = $canvasHeight / $bmp.Height;
$ratio = $ratioY
if($ratioX -le $ratioY){
  $ratio = $ratioX
}

#create resized bitmap
$newWidth = [int] ($bmp.Width*$ratio)
$newHeight = [int] ($bmp.Height*$ratio)
$bmpResized = New-Object System.Drawing.Bitmap($newWidth, $newHeight)
$graph = [System.Drawing.Graphics]::FromImage($bmpResized)

$graph.Clear([System.Drawing.Color]::White)
$graph.DrawImage($bmp,0,0 , $newWidth, $newHeight)

#save to file
$bmpResized.Save($imageTarget,$myImageCodecInfo, $($encoderParams))
$graph.Dispose()
$bmpResized.Dispose()
$bmp.Dispose()

Now suppose that you have saved and named the script above “MakePreviewImages.ps1”. You may use it in a loop statement such as the following one where we assume that MakePreviewImages.ps1 is located under the current directory and the images are in a subfolder called “images”.

Get-ChildItem .images -Recurse -Include *.jpg | Foreach-Object{
   $newName = $_.FullName.Substring(0, $_.FullName.Length - 4) + "_resized.jpg"
     ./MakePreviewImages.ps1 $_.FullName $newName 75
   }

6 thoughts on “Resize image and preserve ratio with Powershell

  1. Ilya

    Awesome script, works like a charm!

    I had to replace all ‘&quot’ to ‘ symbols and that it worked fine

    Reply
  2. Develop3r

    Thanks for the code works great, I want to change the target output to a new folder by setting $imageTarget any thoughts on how I would achieve this

    Reply
    1. benoitpatra Post author

      Yes.
      First your extract the folder full path from the target file name and if it does not exist you create it.
      You can insert something like that at the beginning of MakePreviewImages.ps1

      $folderPath = [System.IO.Path]::GetDirectoryName($targetPath)
      if(!(Test-Path $folderPath)){
         New-Item $folderPath -ItemType Directory
      }
      
      Reply
  3. beebleep

    Thanks for this awesome code, does exactly what i needed! Do you btw know a way to save to the same file i.e. overwrite the source file instead of creating a new one?
    Can I also change the location of the script?

    Get-ChildItem c:\totally\some\where\else -Recurse -Include *.jpg | Foreach-Object{
    $newName = $_.FullName.Substring(0, $_.FullName.Length – 4) + “_resized.jpg”
    c:\mylocation\MakePreviewImages.ps1 $_.FullName $newName 75
    }

    I tried the above but it resulted in ‘out of memory’ errors

    Reply
    1. benoitpatra Post author

      Remind that with Powershell you have access to the .NET framework. This is the case here, we access real .NET Bitmap classes. So the same methods and techniques are available.
      It looks like that the Save() method does not contain a parameter allowing override of an image. You need to delete it on the disk first
      http://stackoverflow.com/questions/8905714/overwrite-existing-image

      I am surprised of your Out of Memory errors. In the script you mention, the images are processed one by one. There is probably a memory leak.
      I am sorry but I do not have time to investigate right now.
      Can you try adding $graph.Dispose() before $bmpResized.Dispose() ?

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *