Optimize PNG images for internet using pngcrush and PowerShell

Reduce size of existing PNG images without loosing quality with pngcrush

Imagine a case where you have an existing website which is running for the past few years. You decided to run a Google PageSpeed insights on couple of pages and you realized that your PNG images on the pages are not optimized for Internet.

Now this can be solved thanks to a lot of online PNG image optimizers, but considering the number of the files you might have over the years of website running it can be matter of days, even maybe weeks to optimize all of them. This was my case and I could not afford myself that much time.

After few minutes of googling I run to a CLI utility for optimizing PNG images called PNGCRUSH. It is an optimizer for PNG images which uses lossless compression, which reduces the file size without affecting the image quality. It is also a crossplatform which means it can be run from a command-line in an MSDOS window, or from a UNIX or LINUX command-line.


D:\Gitlab\powershell-scripts\pngcrush>pngcrush_1_8_11_w64.exe sample.png sample.crushed.png

 | pngcrush-1.8.11
 |    Copyright (C) 1998-2002, 2006-2016 Glenn Randers-Pehrson
 |    Portions Copyright (C) 2005 Greg Roelofs
 | This is a free, open-source program.  Permission is irrevocably
 | granted to everyone to use this version of pngcrush without
 | payment of any fee.
 | Executable name is pngcrush_1_8_11_w64.exe
 | It was built with   bundled libpng-1.6.28
 | and is running with bundled libpng-1.6.28
 |    Copyright (C) 1998-2004, 2006-2016 Glenn Randers-Pehrson,
 |    Copyright (C) 1996, 1997 Andreas Dilger,
 |    Copyright (C) 1995, Guy Eric Schalnat, Group 42 Inc.,
 | and bundled zlib-1.2.11, Copyright (C) 1995-2017,
 |    Jean-loup Gailly and Mark Adler,
 | and using "clock()".
 | It was compiled with gcc version 4.8.0 20121031 (experimental).

  Recompressing IDAT chunks in sample.png to sample.crushed.png
   Total length of data found in critical chunks            =     55036
   Critical chunk length, method   1 (ws 15 fm 0 zl 4 zs 0) =     55169
   Critical chunk length, method   2 (ws 15 fm 1 zl 4 zs 0) >     55169
   Critical chunk length, method   3 (ws 15 fm 5 zl 4 zs 1) >     55169
   Critical chunk length, method   4 (ws 15 fm 0 zl 9 zs 1) >     55169
   Critical chunk length, method   7 (ws 15 fm 0 zl 9 zs 0) =     52378
   Best pngcrush method        =   7 (ws 15 fm 0 zl 9 zs 0) =     52378
     (4.83% critical chunk reduction)
     (4.83% filesize reduction)

CPU time decode 4.286464, encode 4.310737, other 4.290493, total 0.002791 sec

But this tool does not come without limitations and the biggest one is it is unable to overwrite the file which it is optimizing. For this purpose, I rapped it in a PowerShell script, which invokes the PNGCRUSH to do the heavy lifting and then arranges files.

Second issue with PNG crush is that in some cases, if the image is already optimized, it will produce an image which is actually larger than the original. This is also resolved with the PowerShell commands which check the input and output file sizes and then decides whether to use the output or leave the input file.

Note

Update the path to pngcrush utility to the actual location on your machine. Make sure you backup your images folder before running the following PowerShell script to avoid any data loss.

Param(
[Parameter(Mandatory=$true)]
[string]$folder
)
  
$files = get-childitem $folder -recurse -force -include *.png
foreach($inputFile in $files){

   #Temp file path
   $outputFilePath = $inputFile.FullName   ".crushed"

   #Call pngcrush
   $args = $inputFile.FullName   " "   $outputFilePath
   Start-Process -FilePath D:\Gitlab\powershell-scripts\pngcrush\pngcrush_1_8_11_w64.exe -ArgumentList $args -Wait

   $originalFileSize = (Get-Item $inputFile.FullName).length
   $optimizedFileSize = (Get-Item $outputFilePath).length

   if($optimizedFileSize -lt $originalFileSize){
        #Compression reduced file size
        Remove-Item $inputFile.FullName -Force
        Rename-Item $outputFilePath -NewName $inputFile.Name

        #Prepare output
        $savedBytes = $originalFileSize - $optimizedFileSize
        $savedPercentage = [math]::Round(100-($optimizedFileSize / $originalFileSize)*100)
        $message = $inputFile.FullName   " "   $savedBytes   "bytes "   $savedPercentage   "%"

        Write-Output $message
   }
   else{
        #Compression did not reduced file size
        Remove-Item $outputFilePath -Force
   }
}
    

The PowerShell script will provide all the PNG image paths that are in the input folder path including the sub folders to PNGCRUSH utility, so you do not have to worry about the drilling down to the folder structure where you have your images stored.

After the script run you will get the report with image paths and reduction in bytes and % of reduction. If the file is not reduced, it will not report anything in the output console

D:\Gitlab\powershell-scripts>powershell.exe -file Optimize-Png.ps1 -folder D:\Gitlab\powershell-scripts\pngcrush
D:\Gitlab\powershell-scripts\pngcrush\sample.png 2664bytes 5%

D:\Gitlab\powershell-scripts>

PNGCRUSH utility binaries for Windows can be downloaded from sourceforge or you can download it along with the PowerShell script from this article download section.

References

Disclaimer

Purpose of the code contained in snippets or available for download in this article is solely for learning and demo purposes. Author will not be held responsible for any failure or damages caused due to any other usage.


About the author

DEJAN STOJANOVIC

Dejan is a passionate Software Architect/Developer. He is highly experienced in .NET programming platform including ASP.NET MVC and WebApi. He likes working on new technologies and exciting challenging projects

CONNECT WITH DEJAN  Loginlinkedin Logintwitter Logingoogleplus Logingoogleplus

.NET

read more

JavaScript

read more

SQL/T-SQL

read more

Umbraco CMS

read more

Comments for this article