Loading JPEGs to DirectDraw Surfaces

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:

LPDIRECTDRAWSURFACE7 Surface = NULL;

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;
JPEG_CORE_PROPERTIES jcprops;

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:

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.

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:

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.
switch(jcprops.JPGChannels)
{
case 1: jcprops.JPGColor = IJL_G;
break;

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:

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:

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.