Tutorial *122*

Everybody else has already done TGA and PCX Texture loading, but I felt there was something missing. TGAs look cool, but take up too much disk space, whereas PCXs are small but limit you to 8bpp. Using JPGs will give you the best of both.

This code requires the International JPEG Group (IJG) Library to work. This is free to download and use as you want. You'll find instructions on how to use it at http://www.ijg.org. These guys rock. Be aware that including this in your engine will add some 60KB on to the size of the executable.

The following is the code I use to load a JPG into Quake as a texture. It requires 2 things:
1) jpeg_rgba to be defined as a global byte * in the same file as this function.
2) jpeglib.h to be #included in the file.

Please don't ask me anything about how to use the IJG Library - full instructions are included with the download.






byte *LoadJPG (FILE *f, int *width, int *height)


    struct jpeg_decompress_struct cinfo;

    JDIMENSION num_scanlines;


    struct jpeg_error_mgr jerr;

    int numPixels;

    int row_stride;

    byte *out;

    int count;

    int i;

    // set up the decompression.

    cinfo.err = jpeg_std_error(&jerr);

    jpeg_create_decompress (&cinfo);

    // inititalize the source

    jpeg_stdio_src (&cinfo, f);

    // initialize decompression

    (void) jpeg_read_header (&cinfo, TRUE);

    (void) jpeg_start_decompress (&cinfo);

    // set up the width and height for return

    *width = cinfo.image_width;

    *height = cinfo.image_height;

    // initialize the return data

    numPixels = (*width) * (*height);

    jpeg_rgba = malloc ((numPixels * 4));

    // initialize the input buffer - we'll use the in-built memory management routines in the

    // JPEG library because it will automatically free the used memory for us when we destroy

    // the decompression structure. cool.

    row_stride = cinfo.output_width * cinfo.output_components;

    in = (*cinfo.mem->alloc_sarray) ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);

    // bit of error checking

    if (cinfo.output_components != 3)

        goto error;

    if ((numPixels * 4) != ((row_stride * cinfo.output_height) + numPixels))

        goto error;

    // read the jpeg

    count = 0;

    while (cinfo.output_scanline < cinfo.output_height)


        num_scanlines = jpeg_read_scanlines(&cinfo, in, 1);

        out = in[0];

        for (i = 0; i < row_stride;)


            jpeg_rgba[count++] = out[i++];

            jpeg_rgba[count++] = out[i++];

            jpeg_rgba[count++] = out[i++];

            jpeg_rgba[count++] = 255;



    // finish decompression and destroy the jpeg

    (void) jpeg_finish_decompress (&cinfo);

    jpeg_destroy_decompress (&cinfo);

    return jpeg_rgba;


    Con_DPrintf ("Invalid JPEG Format\n");

    jpeg_destroy_decompress (&cinfo);

    return NULL;


You'll notice that I have very rudimentary error checking in here. This is because the IJG Library supports a colossal amount of JPG formats, and is very widely used - you might say it's the Industry Standard. Even Internet Explorer uses it. If you can open a JPG in Internet Explorer, 99.9999% of the time this will work too.

The reason why I declare jpeg_rgba as a global is so that I can free it outside of this function after calling GL_LoadTexture. The reason why I return it from this function is so that I can use a single declaration of byte *data in the one calling function for all image types. I also declare width and height in the calling function as standard ints (not pointers) and pass the addresses to each relevant loading routine. This way, I don't have to worry about the JPG being the same dimensions as the original texture (if I'm using it to replace an original texture), nor do I clutter up my engine with too many globals.

I've just realised that I've contradicted myself there.
Oh well. Do what you want with this code. It all belongs to the IJG anyway. All I did was the jpeg_rgba loading code.

Not logged in
Sign up