GDI+ can't handle some malformed JPG files

I received a comment on my blog VFP handles some images differently with GDIPlus

Here's another problem with the way that VFP hooks into GDI+. If a JPG has malformed EXIF data, VFP will refuse to display it in a standard picture object. It returns error 1108. MS Paint also will choke on this JPG from which I leap to the conclusion that VFP and Paint both take the same route through GDI+. However, Explorer, Word, VB .Net and C# .Net will ignore the bad EXIF data and happily display these images. Which leads me to my second leap of logic and that is there is more than one wya to display a JPG using GDI+.

I found this out when I had a client, who I had recently migrated from VFP 6 to 8, called me complaining about images not showing up but only on certain systems. This, of course, resulted in quite a bit of end-user panic as they thought they had lost a large number of files. Ultimately, I used some tools (VFP and 3rd party) to identify the JPGs that had bad EXIF and remove the EXIF tags. As this was over 12,000 files out of 90,000+, it was a bit of a chore. While I now know how to work around this problem if it rears its ugly head in the future, it would be nice if VFP could call the same image display functions as other MS apps which use GDI+.

From one of my prior posts (see Your VFP runtime splash screen):

In older versions of Foxpro, the handling of JPGs was done internally: there was actually code inside VFP5.exe to decompress a JPG file. However, as the JPG format evolved and the VFP5.exe stayed the same, there were some compatibility issues. With Windows XP, GDIPlus included routines that handled graphics. VFP takes advantage of these OS routines, so that as new graphics formats emerge, an update to GDIPlus will enable VFP to handle them. Also, GDIPlus handles many more graphic formats than VFP5 did.

Here’s an internal email from Trevor (I asked permission to quote him):

This problem is related to our GDIPLUS.DLL, but is not actually caused by it. We have had one other report of this. The Visual FoxPro and GDI+ development teams worked together to discover that the problem was with the JPG, not with the GDI code. With the new GDI+ dll (as you probably know, our GDIPLUS.DLL was updated recently because of a security vulnerability - see https://support.microsoft.com/?kbid=833987) we are doing more stringent checking of image tags, and it seems that the image in that case had an invalid EXIF tag put there by the camera maker. This tag put the JPEG decoder in a weird state for the next operation. In actuality, an error occurs inside the GDI routine because of this bad tag, but it errors silently, so the result was simply a non-display of the picture in VFP9, and a "too big or corrupt" error in 8. The picture may display fine elsewhere (like in Windows Explorer), but this is because it does not follow the same code path inside GDI+ to read the EXIF tags. GDI+ let all this by previously, but has become more persnickety of late, especially given the security hole. I think you're seeing the same thing, especially given that removing the EXIF tags from your picture allows the image to be used.

You said: "These same images display properly when my app is running under W2K or W98 and also display correctly regardless of OS when my app was compiled in VFP 6. I also tested in VFP 7 and it works fine there as well."

This all works in VFP6 and 7 because we did not start using GDI+ for our image work until version 8. Regarding VFP8 on Win98 and W2k, the reason it works there is probably because you distributed the original GDIPLUS.DLL file that came with VFP8. That DLL contained the security hole I mentioned previously, and was also less stringent about EXIF tags. If you were to place the updated GDIPLUS.DLL on those machines (https://www.microsoft.com/downloads/details.aspx?familyid=0aa4b626-f562-428c-a7fd-078826e75513\&displaylang=en), your "bad" images would stop working. It fails on XP because on that OS, VFP (and .EXEs made with VFP) use the OS version of GDIPLUS.DLL (located in \WINDOWS\WinSxS), not it's own. GDI+ is part of the Windows 2003 Server and Windows XP operating systems and was probably updated from WindowsUpdate.com. It is not actually possible to make VFP use a different GDIPLUS.DLL on those operating systems. The plus side of this is that you don't need to distribute GDIPLUS.dll to those operating systems, or update it. The down-side of course is that you cannot revert GDIPLUS.dll to work around issues like this.

As you've found, your workaround to all this is to remove the EXIF data.

I hope this helps Richard. My thanks for supporting FoxPro and good luck with your future projects!

Sincerely,

Trevor Hancock, MCSD VS6 (Fox), MCAD, MCSD .Net

Technical Lead - Microsoft PSS FoxPro Support

Microsoft Visual FoxPro MVP Lead (US)

Writing a blog is simple: just cut and paste! <g>.

PUBLIC oform1

oform1=NEWOBJECT("form")

oform1.SHOW

#define BADPICFILE "D:\PIC_WITH_METADATA.JPG"

WITH oform1

      .AddObject("img","image")

      WITH .img

* .Pictureval=FILETOSTR(BADPICFILE) &&available in VFP9

            .Picture=BADPICFILE

            .Visible= .T.

            .Width= oform1.Width

            .Height= oform1.Height

      ENDWITH

     

ENDWITH

A simpler way to repro:

_screen.Picture = BADPICFILE

I do have a sample of the badly formed JPG, but I do not want to circulate it.

If a JPG file is invalid, it may have different behaviors depending on the client. The VFP Image control can handle multiple image formats that other clients may not and vice versa. Just because an application uses GDIPlus doesn’t mean that all applications will have the same behavior for an invalid file. GDIPlus provides many APIs that can be used in various combinations. For example, a client may want to know if the image contains multiple frames (like an animated GIF: Image::GetFrameDimensionsCount ), has transparency (Image::GetFlags) or may want to convert the image to different format (Bitmap::GetHBITMAP )

If you follow the instructions from this blog to get symbol information from the public symbol server, attach VS 2003 to the process (Tools->Debug Processes), choose Debug->Exceptions->Win32 “Break into Debugger”, then run the sample code, you can see that there is an exception thrown and the debugger will stop the process. The call stack shows:

> kernel32.dll!RaiseException() Line 1532 C

  GdiPlus.dll!jpeg_error_exit() Line 29 C++

  GdiPlus.dll!jpeg_read_header() Line 262 C

  GdiPlus.dll!GpJpegDecoder::ReadJpegHeaders() Line 892 C++

  GdiPlus.dll!GpJpegDecoder::GetImageInfo() Line 4169 C++

  GdiPlus.dll!GpJpegDecoder::CallBeginSink() Line 573 C++

  GdiPlus.dll!GpJpegDecoder::Decode() Line 1030 C++

  GdiPlus.dll!GpDecodedImage::InternalPushIntoSink() Line 432 C++

  GdiPlus.dll!GpDecodedImage::PushIntoSink() Line 408 C++

  GdiPlus.dll!GpMemoryBitmap::InitImageBitmap() Line 746 C++

  GdiPlus.dll!GpMemoryBitmap::CreateFromImage() Line 824 C++

  GdiPlus.dll!CopyOnWriteBitmap::LoadIntoMemory() Line 2349 C++

  GdiPlus.dll!CopyOnWriteBitmap::LockBits() Line 5258 C++

  GdiPlus.dll!GpBitmap::LockBits() Line 9108 C++

  GdiPlus.dll!GpGraphics::DrvDrawImage() Line 1305 C++

  GdiPlus.dll!GpGraphics::DrawImage() Line 2226 C++

  GdiPlus.dll!CopyOnWriteBitmap::CreateHBITMAP() Line 7014 C++

  GdiPlus.dll!GpBitmap::CreateHBITMAP() Line 9769 C++

  GdiPlus.dll!GdipCreateHBITMAPFromBitmap() Line 7554 C++

  vfp9.exe!gdiPlusConvertBitmap() Line 189 C++

  vfp9.exe!GDIPlusLoadPict() Line 814 C++

Back in October 2004 I wrote the following

I believe the next steps are to get somebody on the GDIPLUS team to look at this issue.

 

Below is some standalone C++ code to demonstrate the problem. If you call Gdiplus::Image::GetPropertyCount() before calling GdiPlus.dll!GdipCreateHBITMAPFromBitmap then an exception is thrown and the assert fires. If you answer NO to the messagbox, then Gdiplus::Image::GetPropertyCount() is not called and the assert does not fire.

#include "windows.h"

#include "gdiplus.h"

#include "crtdbg.h"

 

using namespace Gdiplus;

#define Assert _ASSERT

 

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {

      ULONG_PTR gdiplusToken;

      GdiplusStartupInput gdiplusStartupInput;

  HBITMAP hbMap;

      Status gdistat;

 

      gdistat = GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

      _ASSERT(gdistat == Ok);

      Bitmap oBitmap(L"d:\\pic_with_metadata.jpg");

      if (MessageBox(0,"Get Prop Cnt ? ","",4)==6) {

   UINT pcnt;

            pcnt = oBitmap.GetPropertyCount();

      }

      gdistat= oBitmap.GetHBITMAP(Color::Transparent, &hbMap);

      _ASSERT(gdistat == Ok);

      return 0;

}

The call to Image::GetPropertyCount causes the problem. However, this call could be made indirectly by calling some other API.

Trevor also mentions that using the new to VFP9 image.PictureVal property may solve the problem for some malformed JPGs.