View source on GitHub.
A couple of years ago I made a small command-line program for setting the DPI/PPI of PNG files. I used the System.Drawing.Bitmap class (i.e. GDI+) to set this property, which had the unfortunate side effect of producing relatively bloated files. Given that the chunk specifying this property is only 21 bytes long, I thought I could do better.
SetDPI version 2 searches for the pHYs chunk and updates it directly, or adds it immediately before the first IDAT chunk if it doesn’t already exist. Files produced by this tool will be at most 21 bytes larger than the original (or the same size when the pHYs chunk was already present).
13,478 bytes; SHA-1: 800D83A390F2AD80772D79D7FB45C7EAAB0D4294
I’ve mentioned PNGGauntlet many times on this blog – it’s a great tool for squeezing the best compression ratios out of the PNG format. It combines the tools PNGOUT, OptiPNG and DeflOpt in a nice WinForms GUI.
As part of the compression process, most chunks are removed by default. This includes the pHYs chunk, which defines the DPI/PPI of the image – an important property when dealing with WPF. To make sure this chunk isn’t deleted, add ‘pHYs’ under ‘Chunks to keep’ (PNGOUT) and check the ‘Preserve PNG metadata’ box for DeflOpt.
View source on GitHub.
In Part 1 of this series, I discussed the problem of displaying different bitmap images at different DPIs in WPF. In Part 2, I proposed a solution using multi-frame TIFFs and two simple markup extensions. In this final post I will present a basic program that takes multiple images (PNG recommended), gives the option to specify the DPI of each and generates a multi-frame TIFF file accordingly.
I think the UI is self-explanatory. The program attempts to provide an output filename based on the pattern of the names of the input files. Alternatively, you can specify an absolute or relative file URI of your own.
Add files to the list by dragging and dropping them onto the window or click ‘Add Files’. Hovering over an image will display the unscaled image (to a point) in a tooltip, as shown above.
As mentioned in the previous post, avoid using PNGOUT or the like to compress your images – the Windows TIFF decoder has some issues displaying TIFF files made from highly-compressed PNGs.
Apologies to Microsoft for nicking an icon from imageres.dll.
The download includes the program binary (signed) and source (which isn’t particularly well structured or documented).
156,593 bytes; SHA-1: 4CD2D2A7F7CE124A95D7900CBE89CAA1E0310790
View source on GitHub.
Ken Silverman’s PNG compression tool PNGOUT (complemented nicely by the free .NET frontend PNGGauntlet) can be remarkably effective at trimming the size of PNGs without altering the image described within.
However, in its quest to remove anything non-essential, PNGOUT by default strips out the image’s DPI (in fact PPI) information. PNGs without PPI information will be treated differently by different software.
WPF either uses a default of 96 or the current system DPI with such images (I’m not sure which, but the latter makes more sense). Sometimes this can have nice side-effects, as Scott Hanselman discovered – images that were designed for 96 PPI but set to 72 PPI were suddenly ‘fixed’ (at least when the application was run in a 96 DPI environment). Better than relying on WPF’s interpretation of PNGs without PPI information is to correctly set the PPI in the first place. For example, if an image is designed for 120 DPI but has its PPI set to 96, WPF will (correctly) try and scale the image, which is clearly not desirable.
PNGOUT features a command line option /k# for removing or keeping optional chunks. The pHYs chunk holds the PPI information, which is what we want to leave alone. Using the command line option /kpHYs with PNGOUT will thus preserve PPI information. (Information from WulfTheSaxon.)
Sometimes, though, it is useful to have a utility that sets PPI information for lots of images at once (the PPI information in the PNGs may not be correct before using PNGOUT, for instance, rendering the /kpHYs switch pointless).
Josip Medved had the same thought and created a tool for setting the PPI to 96 for many images at once.
I decided to slightly extend his tool to take the desired horizontal and vertical PPI as command line arguments. The source and binaries can be downloaded here (SHA-1: 800D83A390F2AD80772D79D7FB45C7EAAB0D4294). The usage is SetDPI dpiX dpiY filepattern1 [filepattern2 […]].
- SetDPI 96 96 *.png (sets all PNG files to 96 PPI)
- SetDPI 120 120 a.png b.png c.png (sets a.png, b.png and c.png to 120 PPI)
- SetDPI 144 144 C:Images*.png (sets all PNG images in directory C:Images to 144 PPI)