.NET - Bitmap.Save ignores Bitmap.SetResolution on Windows 7

10,234

Solution 1

Hmya, this is a bug in a Windows component. The Windows group is always very reluctant to get bugs like this fixed, breaking changes are postponed to a next Windows version. It did get fixed in Windows 8. Do consider how unusual it is what you are doing, the DPI of an image should always be set by the device that recorded the image. Like the camera or scanner, they never get this wrong. There just isn't any device around that has a 200 dots-per-inch resolution.

If you are desperate enough to find a workaround then you could consider patching the file itself. Not hard to do for a JPEG file, the fields in the file header are pretty easy to get to:

using System.IO;
...
    public static void SetJpegResolution(string path, int dpi) {
        using (var jpg = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) 
        using (var br = new BinaryReader(jpg)) {
            bool ok = br.ReadUInt16() == 0xd8ff;        // Check header
            ok = ok && br.ReadUInt16() == 0xe0ff;
            br.ReadInt16();                             // Skip length
            ok = ok && br.ReadUInt32() == 0x4649464a;   // Should be JFIF
            ok = ok && br.ReadByte() == 0;
            ok = ok && br.ReadByte() == 0x01;           // Major version should be 1
            br.ReadByte();                              // Skip minor version
            byte density = br.ReadByte();
            ok = ok && (density == 1 || density == 2);
            if (!ok) throw new Exception("Not a valid JPEG file");
            if (density == 2) dpi = (int)Math.Round(dpi / 2.56);
            var bigendian = BitConverter.GetBytes((short)dpi);
            Array.Reverse(bigendian);
            jpg.Write(bigendian, 0, 2);
            jpg.Write(bigendian, 0, 2);
        }
    }

Solution 2

I've found a workaround that will do the job. It's not elegant but...

Instead of applying the resolution to the original image, make a copy of it and work on the copy:

Bitmap bitmap = (Bitmap)Image.FromFile(originalFile);
Bitmap newBitmap = new Bitmap(bitmap)
newBitmap.SetResolution(200, 200);
newBitmap.Save(newFile, ImageFormat.Jpeg);

Now it works on Windows 7. Go figure.

I like Hans Passant's idea, though, it's cleaner. I don't know if what I did messes up with the image, if there is recompression or not.

Share:
10,234
Admin
Author by

Admin

Updated on June 05, 2022

Comments

  • Admin
    Admin almost 2 years

    I'm writing a .NET 4 application that imports and saves images for printing. It's important that the saved images resolution (DPI not pixel dimensions) be set to the value we specify so they print correctly.

    Some of the images we import come without the resolution value (bad EXIF when they were generated), so we have to correct that before writing them. We use Bitmap.SetResolution for that. It works fine on XP and Windows 8, but when we write (Bitmap.Save) the images on Windows 7, they are always written with the original resolution meta info, ignoring SetResolution.

    Here's a test we made, works on XP and 8, not on 7.

    string originalFile = @"D:\temp\img\original_img.jpg";
    string newFile = @"D:\temp\img\new_img.jpg";
    
    Bitmap bitmap = (Bitmap)Image.FromFile(originalFile);
    bitmap.SetResolution(200, 200);
    bitmap.Save(newFile, ImageFormat.Jpeg);
    
    Image image = Image.FromFile(newFile);
    int dpiX = (int)Math.Round(image.HorizontalResolution, MidpointRounding.ToEven);
    int dpiY = (int)Math.Round(image.VerticalResolution, MidpointRounding.ToEven);
    Console.WriteLine("DPI is {0} x {1}", dpiX, dpiY);
    

    Before saving, debug always shows the correct resolution assigned by SetResolution, the saved image is where the problem is.

    This is probably what was reported here: http://social.msdn.microsoft.com/Forums/vstudio/en-US/62368caa-05f4-4798-9c59-5d82f881a97c/systemdrawingbitmapsetresolution-is-completely-broken-on-windows-7?forum=netfxbcl

    But the issue there seems to remain unsolved. Is there really no way to just make it work? Do I have to use extra libraries for this?

  • Admin
    Admin over 10 years
    I'm working with dental x-rays, so images have very high resolutions, often over 1000 DPI. And, believe-me, some systems that I've encountered leave x-resolution and y-resolution info blank (I've checked the images out with IrfanView).Thanks for the answer, I'll look into it. Meanwhile, I've found a hack I'm posting here that does the job.
  • Admin
    Admin over 10 years
    Oh, and I know how messy it can get when you let people choose the resolution of their images. We'll have to calibrate the value for each imaging device.
  • user1703401
    user1703401 about 10 years
    No, this is for JPEG. PNG requires patching the pHYs chunk.
  • Renzo
    Renzo over 9 years
    This fix works and doesn't require messing with the image's binary. Just remember to put those bitmaps in using blocks so they get freed.
  • rayzinnz
    rayzinnz over 7 years
    Nice, except the newBitmap does not copy the bitmap color depth
  • Remigijus Pankevičius
    Remigijus Pankevičius over 2 years
    8 years later, now on Windows 10 and I still need to find this. "The Windows group is always very reluctant to get bugs like this fixed..." as Hans Passant wrote. True, "always" continues, because it's "always".