Saturday, December 1, 2012

Loading images under Windows



Previous parts
  Today I will describe how I managed loading of images for Win platform in my small cross-platform mobile engine. If you wonder why I support desktop Win in mobile engine read this: Simple cross-platform game engine - Introduction. But shortly: Win with OpenGL ES emulation is not primary target for me. It is convenient way how to get the game run fast, easy way how to share progress with team mates, comfortable debugging, ...

 Before you can create texture in OpenGL you have to get the texture data somehow. As Android platform in my engine can handle both .png and .jpg I wanted to have the same in Win. Finally I got 2 ways working on my PC through:

 Microsoft WIC

 WIC was the first way. The following routine will load image (.png, .jpg, ...) and return unsigned char* to raw RGBA data. You can take this data and create texture from it. You will also have to add library windowscodecs.lib.

1. convert char* file name to  WCHAR
u8* AssetLoader::loadImage(s32 aIdx, u32& aWidth, u32& aHeight)
{
 // convert string to wchar
 int len = strlen(mActualFileName);
 if (len + 1 > 256)
  LOGE("ERROR loading file - filename is loner than 256 characters");
 // long enough
 WCHAR wFileName[256];
 MultiByteToWideChar(0, 0, mActualFileName, -1, wFileName, 256);

2. prepare variables and initialize WIC Imaging Factory
 HRESULT hr = S_OK;
 IWICImagingFactory* pImagingFactory = NULL;
 IWICBitmapDecoder* pIDecoder = NULL;
 IWICBitmapFrameDecode* pIDecoderFrame  = NULL;
 IWICFormatConverter* pIFormatConverter = NULL;

 hr = CoCreateInstance(
  CLSID_WICImagingFactory,
  NULL,
  CLSCTX_INPROC_SERVER,
  IID_IWICImagingFactory,
  (LPVOID*) &pImagingFactory);

3. create decoder and decode first frame of the image
 // Create the decoder.
 if (SUCCEEDED(hr))
  hr = pImagingFactory->CreateDecoderFromFilename(
   wFileName,                      // previously created filename
   NULL,                           // Do not prefer a particular vendor
   GENERIC_READ,                   // Desired read access to the file
   WICDecodeMetadataCacheOnDemand, // Cache metadata when needed
   &pIDecoder                      // pointer to created decoder
   );

 // Retrieve the first bitmap frame.
 if (SUCCEEDED(hr))
  hr = pIDecoder->GetFrame(0, &pIDecoderFrame);

4. create and initialize the convertor. We will use it to convert the image into RGBA format
 // Create convertor.
 if (SUCCEEDED(hr))
  hr = pImagingFactory->CreateFormatConverter(&pIFormatConverter);

 // Initialize the format converter.
 if (SUCCEEDED(hr))
  hr = pIFormatConverter->Initialize(
  pIDecoderFrame,                  // Input source to convert
  GUID_WICPixelFormat32bppPRGBA,   // Destination pixel format
  WICBitmapDitherTypeNone,         // Specified dither pattern
  NULL,                            // Specify a particular palette 
  0.f,                             // Alpha threshold
  WICBitmapPaletteTypeCustom       // Palette translation type
  );

5. convert the image and copy its pixels
 // get pixels in RGBA format
 u8* buffer = NULL;
 if (SUCCEEDED(hr))
 {
  pIFormatConverter->GetSize(&aWidth, &aHeight);
  buffer = new u8[aWidth * aHeight * 4];
  pIFormatConverter->CopyPixels(0, aWidth * 4, aWidth * aHeight * 4, buffer);
 }

6. clean and return
 pIFormatConverter->Release();
 pIDecoderFrame->Release();
 pIDecoder->Release();

 return buffer;
}

 In this way the loading of images worked fine for me. But our graphician got only black screen. And you will agree that black screen for someone whou should see how his graphics looks in game like is not good at all. It seems that WIC is supported from Windows XP SP3. And "something missing" is probably the reason why it returned only black screen. As I had no opportunity to test on his computer I started to look for something that is less dependant on windows versions. I found open source library FreeImage.

FreeImage

 FreeImage has very clearly and well written documentation with lot of examples. The library is capable of many things but my only target was to load image (regardless the format at the best). So, to make FreeImage work download the header, .lib file and .dll. Set path to header and to FreeImage.lib file.

1. add header and create variables
#include <FreeImage.h>

u8* AssetLoader::loadImage(s32 aIdx, u32& aWidth, u32& aHeight)
{
 FIBITMAP* bitmap = NULL;
 FREE_IMAGE_FORMAT fif = FIF_UNKNOWN;

2. load the image. This part was directly taken from documentation with copy&paste (just very slightly adjusted). You can see it is very simple to load image while checking lot of situations
 // check the file signature and deduce its format
 // (the second argument is currently not used by FreeImage)
 fif = FreeImage_GetFileType(mActualFileName, 0);
 if(fif == FIF_UNKNOWN)
 {
  // no signature ?
  // try to guess the file format from the file extension
  fif = FreeImage_GetFIFFromFilename(mActualFileName);
 }
 
 // check that the plugin has reading capabilities ...
 if((fif != FIF_UNKNOWN) && FreeImage_FIFSupportsReading(fif))
 {
  // ok, let's load the file
  bitmap = FreeImage_Load(fif, mActualFileName, 0);
  // unless a bad file format, we are done !
  if (!bitmap)
  {
   LOGE("loading bitmap %s failed", mActualFileName);
   return NULL;
  }
 }

3. convert image into 32bpp RGBA format
 // convert to 32bpp
 FIBITMAP* bitmap32 = FreeImage_ConvertTo32Bits(bitmap);
 // FreeImage bitmaps are always upside down
 FreeImage_FlipVertical(bitmap32);
 //retrieve the image data and get the image width and height
 RGBQUAD* bits = (RGBQUAD*) FreeImage_GetBits(bitmap32);
 aWidth = FreeImage_GetWidth(bitmap32);
 aHeight = FreeImage_GetHeight(bitmap32);
 // check results
 if((bits == NULL) || (aWidth <= 0) || (aHeight <= 0))
 {
  LOGE("bitmap is somehow corrupted (width=%i, height=%i, bits=%i)", aWidth, aHeight, bits);
  return NULL;
 }

4. switch red and blue color
 u8* buffer = new u8[aWidth * aHeight * 4];
 RGBQUAD* dest = (RGBQUAD*) buffer;

 for(u32 i = 0; i < aHeight; i++)
 {
  for (u32 j = 0; j < aWidth; j++)
  {
   RGBQUAD colorQuad = *(bits ++);

   // swap red and blue
   BYTE rgbTemp = colorQuad.rgbBlue;
   colorQuad.rgbBlue = colorQuad.rgbRed;
   colorQuad.rgbRed = rgbTemp;

   *(dest ++) = colorQuad;
  }
 }

5. clean and return data
 // clean
 FreeImage_Unload(bitmap);
 FreeImage_Unload(bitmap32);

 return buffer;
}

 When first compiling I had problem saying that RGBQUAD is not defined. RGBQUAD is structure defined in wingdi.h which is included in windows.h. As I have NOGDI defined in my project (for some reasons) this structure was undefined. I had to alter slightly the FreeImage.h header file to overcome this. This part ...
 :
 : 
typedef unsigned __int64 UINT64;
#endif // _MSC_VER

#if (defined(_WIN32) || defined(__WIN32__))
#pragma pack(push, 1)
#else
#pragma pack(1)
#endif // WIN32

typedef struct tagRGBQUAD {
#if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR 
 :
 : 

 ... was changed to this:
 :
 :
typedef unsigned __int64 UINT64;
#endif // _MSC_VER
#endif // _WINDOWS_                                                *******

#if (defined(_WIN32) || defined(__WIN32__))
#pragma pack(push, 1)
#else
#pragma pack(1)
#endif // WIN32

#if !defined(_WINDOWS_) || (defined(_WINDOWS_) && defined(NOGDI))  *******
typedef struct tagRGBQUAD {
#if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR
 :
 :

 So, here are two ways how you can get image data for your textures. Next time I will write how is this achieved for Android platform in my engine using NDK and JNI.


No comments:

Post a Comment