View on GitHub

Windows Jpeg DPI Issue

The Issue

Windows has a problem displaying certain JPEG images. This problem typically manifests as "Windows Photo Viewer stretches my images". An example of an extreme case:

one-pixel

There are various threads out in the InterWebs complaining about the issue. E.g. here , and here. Google for "Windows Photo Viewer thin line" and you'll get plenty more. The problem is not limited to Windows' built-in utilities: it also shows up for us image application developers. The reason is the problem is in the Windows image libraries apon which many image applications are built.

For the typical user, often the problem first showed up when they upgraded from Windows XP to a newer version of Windows. The Windows XP utility "Windows Picture and Fax Viewer" did not show the problem. The Windows Vista and later utility, "Windows Photo Viewer", is impacted by the problem. On Windows 10, the "Photo Gallery" app doesn't have the problem, but the "Windows Photo Viewer" on Windows 10 does.

For said typical user, there are only two possible solutions:

  1. View the images with a different program.
  2. Fix the images.

The InterWebs threads suggest more than one possible cause for the problem. I cannot comment regarding the validity of the "incorrect EXIF data" suggestion, as I've not encountered any instance of said images. I can however confirm that JPEG images with strange DPI encodings will confuse "Windows Photo Viewer", and other Microsoft applications.

The JPEG Standard re DPI

On page 5 of the JPEG standard (here), the header is described. Three fields are behind this problem: the Xdensity, Ydensity, and units. Here is a screencap of the relevant details (highlighted):

cap

For unknown reasons, some image editing applications have stored inconsistent, or inappropriate, values in those fields. The Windows image libraries attempt to honor those values, usually with poor results.

Fixing Your Code

You'll never encounter the problem if you always specify the output height and width when drawing images (or if you don't have any buggy JPEGs!). But if you rely on System.Windows.Media.Imaging.BitmapImage or Graphics.DrawImage to correctly "guess" the destination size of images, the problem will occur. By specifying the output dimensions of the image, you force the Image libraries to ignore the DPI values inside the image.

Example of problematic code (Graphics.DrawImage):

Bitmap imageFromFile; // image loaded from a file
Bitmap destImage;     // image being drawn to
using (Graphics gg = Graphics.FromImage(destImage))
{
	gg.DrawImage(imageFromFile, destX, destY);
}

and a working version:

Bitmap imageFromFile; // image loaded from a file
Bitmap destImage;     // image being drawn to
int destW = imageFromFile.Width; // desired output size
int destH = imageFromFile.Height; // desired output size
using (Graphics gg = Graphics.FromImage(destImage))
{
	gg.DrawImage(imageFromFile, destX, destY, destW, destH);
}

Straight from the Microsoft examples, the following WPF fragments will display problematic images badly (first the XAML version, then the code-behind variant):

Image myImage = new Image();
myImage.Width = 200;

// Create source
BitmapImage myBitmapImage = new BitmapImage();

// BitmapImage.UriSource must be in a BeginInit/EndInit block
myBitmapImage.BeginInit();
myBitmapImage.UriSource = new Uri(@"C:\Documents and Settings\All Users\Documents\My Pictures\Sample Pictures\Water Lilies.jpg");
myBitmapImage.DecodePixelWidth = 200;
myBitmapImage.EndInit();
//set image source
myImage.Source = myBitmapImage;

N.B. specifying the DecodePixelWidth value does not solve the problem.

An example of fixed code for WPF is too long to show here. In my projects, I'm using an ImageConverter, and in that code, performing convolutions to specify the image dimensions. See the BooruTagger repository, ImageConverter.cs.

Identifying Problem Images

In my testing, I have found the following scenarios:

units = 0 No issues
units = 1; Xdensity or Ydensity is zero No issues
units = 1; Xdensity or Ydensity is 1 Displays as a very narrow image
units = 1; Xdensity != Ydensity Displays stretched or distorted
units = 1; Xdensity == Ydensity, value is > 100 May display tiny (image DPI is > screen DPI of 96)

Annoyingly, various tools which might easily identify an image's DPI settings, do a poor job. Specifically, Windows Explorer never shows the two values, only the one "valid" value. The identify program (part of ImageMagick) also fails to provide useful data, even in -verbose mode.

So I wrote my own. The code below is the guts of the "is this a problem image" test:

const int READ_SIZE = 32;
byte[] bom = new byte[READ_SIZE];

using (FileStream fileStream = File.OpenRead(afile))
{
	fileStream.Read(bom, 0, READ_SIZE);
	for (int i = 0; i < READ_SIZE - 4; i++)
	{
		// search for 'JFIF' signature
		if (bom[i] != 'J' || bom[i + 1] != 'F' || 
		    bom[i + 2] != 'I' || bom[i + 3] != 'F') 
			continue;

		// found it.
		int units = bom[i + 7];
		int xd = bom[i + 8] * 256 + bom[i + 9];
		int yd = bom[i + 10] * 256 + bom[i + 11];
		if (units == 0) // zero for units appears not to be a problem
			break; // don't search any further
		if (xd != yd)
		{
			if (xd == 1 || yd == 1)
				Console.WriteLine("Thin image: {0} ({3}:{1}x{2})", afile, xd, yd, units);
			else if (yd != 0) // zero for ydensity appears not to be a problem
				Console.WriteLine("Stretch image: {0} ({3}:{1}x{2})", afile, xd, yd, units);
		}

		break; // don't look any further [at least one image with two JFIF signatures...]
	}

For the rest of the code, see my JFIF_Scanner repository. It doesn't identify images where the DPI is way out of whack compared to the screen DPI of 96.

How many 'problem' images did I have? 637 "thin" images (width of 1 pixel) and 407 "stretched" images - X and Y DPI values out of sync. Out of ... 473,000+ jpeg files.