Jump to content

Loading JPEGs to DirectDraw Surfaces

image jcprops surface jpeg buffer file ddsd bitmap
Download Attachments

4: Adsense

In order to keep the size of this article down, I've decided to make a few assumptions. First of all, I assume that you already know C/C++ and how to troubleshoot and debug code. I also assume that you are somewhat familiar with DirectDraw and that you have as a minimum the DirectX 7.0 libraries and the ability to work in 24 bit. Note: the source code in EXAMPLE.ZIP available with this article provides conversions to 16bit and 32bit surfaces.

The first step to loading JPEGs is to download the Intel JPEG Library from Intel's website. The Library is loaded with documentation and examples, all of which we're really not interested in. What we really want are the IJL.H, IJL15.LIB, and IJL15.DLL files that come with the package. Once you have those files, include the IJL.H header to your source file, add the IJL15.LIB file to your project, and make sure the IJL15.DLL is in a valid location such as the C:\WINDOWS\SYSTEM folder.

There are a few more things we need to do before beginning. We need to make sure that we have a Direct Draw Surface to work with:


We need to also be sure to set our display bit depth to 24 bit:

DDObject->SetDisplayMode(640, 480, 24, 0, 0);

We're now ready to load a JPEG to our surface. Since we're using the Intel JPEG Library, we need to create a couple of objects:

IJLERR jerr;

IJLERR holds return information for determining a pass or fail status. JPEG_CORE_PROPERTIES is our JPEG object. Once we have these two objects, we are ready to initialize them:

jerr = ijlInit(&jcprops);
if (jerr != IJL_OK)
   //report initialization error

The ijlInit function call initializes the JPEG_CORE_PROPERTIES object. We can check the status of this function call by testing whether or not our IJLERR object was initialized with the value IJL_OK.

At this point, we must decide if we are going to load our JPEG image from a file or from a buffer. Because loading from a file takes fewer steps, we will do that here. However, I give an example of loading from both in the EXAMPLE.ZIP file included with this article. We load from a file by changing our JPEG object's JPGFile member to a file name. We then call ijlRead to retrieve the file parameters.

jcprops.JPGFile = FileName;
jerr = ijlRead(&jcprops, IJL_JFILE_READPARAMS);
if (jerr != IJL_OK)
   //report read error

This initial read fills our JPEG object with information about the file we are going to load. What we must now do is find a way of converting the JPEG to a device independent bitmap (DIB) so that we can attach it to our Direct Draw surface.

We start by creating a buffer to hold our image data. After the buffer is created, we must resize it to fit a 24Bit image:

//Prepare a 24Bit buffer to receive image data
BYTE *buffer24;
//Determine the required size
long szbuff24 = (jcprops.JPGWidth * 24 + 7) / 8
    * jcprops.JPGHeight;

//Resize the buffer and check for null
buffer24 = new BYTE [szbuff24];
if (buffer24 == NULL)
    //Report memory allocation error

Now we need to fill in the DIB portion of the JPEG object to get ready for the conversion from JPEG to DIB.

jcprops.DIBWidth	= jcprops.JPGWidth;
jcprops.DIBHeight   = jcprops.JPGHeight; //Implies a bottom-up DIB.
jcprops.DIBChannels = 3;
jcprops.DIBColor	= IJL_BGR;
jcprops.DIBPadBytes = IJL_DIB_PAD_BYTES(jcprops.JPGWidth, 3);
jcprops.DIBBytes	= reinterpret_cast <BYTE*> (buffer24);

Let's look at some of these a little closer. The DIBBytes member points to the buffer that we created. When we retrieve the JPEG data, the information we get will be stored in this buffer. The DIBWidth and DIBHeight members specify the size of the DIB. The DIBColor member specifies that we want our image data in reverse order Blue Green Red. That's the way that DIBs are actually stored. They are also stored upside down. You can flip the retrieved image by negating the DIBHeight member:

//This is what you should do if you find your images are coming out upside down.
jcprops.DIBHeight = - jcprops.JPGHeight;

Before we read in the image, we have to check one more thing: the JPG color space:

//Set the JPG color space ... this will always be somewhat of an
//educated guess at best because JPEG is "color blind" (i.e.,
//nothing in the bit stream tells you what color space the data was
//encoded from.
  case 1: jcprops.JPGColor = IJL_G;
  case 3: jcprops.JPGColor = IJL_YCBCR;
	//This catches everything else, but no color twist will be
	//performed by the IJL.
	jcprops.DIBColor = (IJL_COLOR)IJL_OTHER;
	jcprops.JPGColor = (IJL_COLOR)IJL_OTHER;

We are finally ready to retrieve the actual JPEG image. Thanks to Intel's JPEG Library - this is a trivial task:

//Read in image from file
jerr = ijlRead(&jcprops, IJL_JFILE_READWHOLEIMAGE);
if (jerr != IJL_OK)
   //Report read error

This function copies the image information into our buffer. At this point, if we were to insert a BITMAPFILEHEADER and a BITMAPINFOHEADER at the front of our buffer, we could dump the buffer to a binary file. This would effectively create a bitmap file saved to disk. However, we instead want to turn our image into a DIB and attach it to a Direct Draw surface. Therefore, we use the Windows API function CreateBitmap to build our DIB:


//Create the bitmap and get a handle to it
hbm = CreateBitmap (jcprops.JPGWidth, jcprops.JPGHeight, 1, 24, buffer24);
if(hbm == NULL)
   //Report failure to create bitmap

The CreateBitmap function takes the dimensions of the image, the number of channels, the number of bits per pixel, and the color bit information from our bitmap buffer and creates a bitmap for us. Upon success, we are given a handle to the newly created bitmap.

Before we go any further, we need to make sure that we have a Direct Draw surface to copy our bitmap to. Set up the Direct Draw surface description and create the surface:


ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize   = sizeof(ddsd);
ddsd.dwWidth  = jcprops.JPGWidth;
ddsd.dwHeight = jcprops.JPGHeight;

Result = DDObject->CreateSurface(&ddsd, &Surface, NULL);
if (Result != DD_OK)
   //Report surface creation error

Now, all that is left is to copy our bitmap over to our Direct Draw surface. Fortunately, there is a function provided by Direct Draw that does just that. It can be found in the DDUTILS.CPP file:

DDCopyBitmap(Surface, hbm, 0, 0, 0, 0);

Before we test our image out, let's clean up some things that we don't need any more:

//We no longer need our image buffer
delete buffer24;

//Release the JPEG object

Finally, the time has come to take our image for a test drive:

RECT Image;

//Reset surface description
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof( ddsd );

//Get the surface description so that we can dynamically
//find the width and height of our surface
Result = Surface->GetSurfaceDesc(&ddsd);
if (Result == DD_OK)
  //Coordinates of image size
  Image.left   = 0;
  Image.top	= 0;
  Image.right  = ddsd.dwWidth;
  Image.bottom = ddsd.dwHeight;
  //Blit image to back buffer
  while (true)
	Result = BackBuffer->BltFast (0, 0, Surface, &Image,
                              	DDBLTFAST_WAIT | DDBLTFAST_NOCOLORKEY);
	if( Result == DD_OK )  break;
	if( Result == DDERR_SURFACELOST )
	{  Result = RestoreAll();
   	if( Result != DD_OK ) break; }
	if( Result != DDERR_WASSTILLDRAWING ) break;

If everything goes smoothly, you should see your image pop up on the screen. Keep in mind that you still have to release your surface when you no longer need it, and that you may have to restore it as a result of ALT+TAB. You can restore the surface by following these exact steps, however, you will not need to create the surface again.

Good luck, and have fun with JPEGs!

Johnny Wood


Note: GameDev.net moderates article comments.