Why the Same Screenshot is Saved as PNG of Different Sizes by Different Tools

Different applications like Gimp and Windows Paint create PNG files of different sizes for the same screenshot. I am going to show how to parse PNG files by using Python and try to figure out the reason for the difference.

Introduction

PNG is one of the commonly used image formats on the Internet. I used to save screenshots as PNG files. Unlike normal photos, screenshots of software applications usually have less colors. Using PNG, we can easily get images with good qualities and small file sizes. What’s more, it’s an open source format. For these reasons, I prefer PNG to other formats when creating screenshots.

To be honest, I hadn’t paid much attention to the actual sizes of PNG files generated by software tools until recently when I need to create some PNG files for my blog and web pages. I mainly use two software tools to create screenshots which are Gimp and Windows Paint. I assumed that these tools would generate PNG files of optimal sizes. Whereas, I noticed recently that Gimp and Windows Paint generate PNG of different sizes of the same screenshot. Although there are no options about PNG format in Windows Paint, it still creates smaller PNG files than Gimp with the highest compression level selected. Then I was curious about the difference. So I did some experiments. In this post, I am going to share something I have learned about PNG from the process.

PNG format

Here, PNG refers to Portable Network Graphics. It’s a raster graphics file format. It was created as a replacement to GIF which uses a patented compression algorithm LZW. Please refer to the Wikipedia for more details of PNG format.

Basically a PNG file starts with 8-byte header followed by chunks. Each chunk consists of four parts:

  1. Length of the chunk – 4 bytes
  2. Chunk type – 4 bytes
  3. Chunk data – (Length of the chunk) bytes
  4. CRC – 4 bytes

That is, if the first four bytes of a chunk is 8192 as an integer, it means that the data of the chunk has 8192 bytes, and the full size of the chunk is 4+4+8192+4 = 8204 bytes.

The second four bytes of a chunk indicates the type of the chunk, which is the case sensitive chunk type in ASCII format. A valid PNG file must have at least the following four critical chunk types:

  1. IHDR: Must be the first chunk. It contains the meta information about the image like width, height etc
  2. PLTE or sRGB: List of colors or the standard sRGB colors depending on the type of PNG
  3. IDAT: Image data
  4. IEND: Marks the end of the image

A PNG file can contain multiple IDAT chunks. To decode a PNG file, all IDAT chunks in a PNG file will be concatenated and the combined IDAT data is decompressed to give the image.

The following is a screenshot of TotalCommander Lister showing a PNG file in Hex format. We can see the first 8-byte header followed by IHDR and sRGB chunks:

A PNG file shown in TotalCommander Lister in Hex
A PNG file shown in Hex

Parse PNG file using Python

Firstly, let’s create a class to model chunks in a PNG file:

 

To decode a PNG file, firstly open the file, read the content into a buffer, then decode the header and chunks:

 

A valid PNG file contains at least one IDAT chunk. IDAT chunks contain compressed image data, which is a rectangular pixel array. Each pixel consists of color information. There are mainly three types of pixels in PNG files:

  1. Grayscale pixel. Minimum pixel size: 1 bit
  2. Color-indexed pixel. Minimum pixel size: 1 bit
  3. Truecolor pixel: Minimum pixel size: 24-bit

Optionally, each pixel can have transparence information which is 8-bit. Therefore, a truecolor pixel with transparence consists of 4 bytes.

Image data is organized from the top to bottom. Each row of the image data is packed from the left to the right to form a scanline. Each scanline always starts with a single byte indicating the filter type followed by the packed pixel array of the image. All scanlines are then compressed by using DEFLATE algorithm which is the same as in zlib. We can use zlib module in Python to decompress data in IDAT chunk as shown below:

 

Please refer to zlib RFC page for more details of the ZLIB compress data format specification. Basically zlib compressed data always has two bytes in the beginning to indicate compression information. For example, the first two bytes of zlib compressed data would appear to be “78 da” in hexadecimal. From these two bytes, we can guess some parameters used in DEFLATE algorithm. But can only get possible values for some parameters like compression level, because different compression level may result in the same header values in the compressed data. In order to figure out the actual compression parameters, we can use a try and error method to test the combination of parameters with commonly used values in Python.

There is already a Python module to parse PNG file. But because I want to test different data compression parameters, I wrote a Python script for the purpose. The complete Python script file can be downloaded from here. The downloaded Python script, sspng.py, works with Python 2.7 and 3.4. It accepts one argument as the path to a png file, dumps the image mata information, and tries to figure out the compression parameters used to create the PNG file. If failed, it prints the actual IDAT sizes if using the compression parameters used in the test.

Exampels

Let’s take a screenshot of TotalCommander on Windows 10 x64 as an example, which is shown below.

Screenshot of TotalCommander 8.52a on Windows 10
Screenshot of TotalCommander 8.52a on Windows 10

To take the screenshot, bring TotalCommander to the front on the desktop, press Alt-PrtScrn. It will copy the screenshot of TotalCommander to the clipboard. Then paste the image in applications which we want to use to edit and save the screenshot. Here, I am going to use Gimp v2.9 development version and the default Windows Paint. When saving the screenshot as a PNG in these two applications, there is no option to select in Paint regarding PNG file, whereas there are options including the compression level in Gimp to select. Take the default options in Gimp which means to use the highest compression level 9. The generated file sizes are shown below:

  • Paint: 125,149 bytes
  • Gimp: 162,688 bytes

Assume the PNG file of the above screenshot created by Gimp is saved as c:\users\wdong\Pictures\totalcmd-en-gimp.png, we can run sspng.py on it as shown below:

 

 

From the above information, we know that the image height is 793 pixels. This means that there are 793 scanlines in IDAT chunk. The color type is 2 so there are 3 bytes (24 bits) per pixel. The first byte of each scanline contains filtering information. The uncompressed image data has (1+1010*3)*793=2403583 bytes. The compressed data is split into multiple IDAT chunks in the file. Each IDAT chunk contains 8192 bytes. The total compressed image data has 162326 bytes. It seems that Gimp uses the standard DEFLATE algorithm to compress the image data when creating PNG file, for we can get the same sized data by compressing the same image data using zlib module of Python.

Running the script on the screenshot created by Windows Paint shows the following information:

 

This means that we can’t reproduce the same compressed data by using the standard implementation of DEFLATE algorithm using the zlib module in Python. Compare the above test results, we can see the following differences between PNG files created by Gimp and Windows Paint on Windows 10:

  • IDAT chunks are in 8192-byte blocks in Gimp and 65445-byte blocks in Paint
  • Windows Paint on Windows 10 must have applied a kind of pre-compression filtering to optimize the PNG file.
  • Windows Paint on Windows 10 doesn’t use the optimal compression parameters.

Further experiments with Gimp 2.8 and Windows Paint on Windows 7 have shown that those applications use the standard DEFLATE algorithm to compress data in PNG files.

Optimize PNG files

Basically, there are three ways to optimize PNG files, namely,

  1. Use indexed colors instead of truecolors
  2. Filter image data before compression
  3. Use different implementations of DEFLATE algorithm which give better compression ratios, ie, 7-zip DEFLATE and Zopfli released by Google

Please refer to the following pages for more information about how to optimize PNG files:

  1. https://zoompf.com/blog/2010/01/top-png-optimizers-dont-use-zlib
  2. http://www.smashingmagazine.com/2009/07/clever-png-optimization-techniques/

Leave a comment

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