Xlib JPEG Display

This document may be copied and distributed provided that George Peter Staplin is given credit.

This tutorial assumes that the reader knows about C and/or C++, creating makefiles, and compilation flags. If you are not familiar with creating makefiles and compilation flags read my tutorial that covers these. This tutorial requires some previous knowledge of Xlib, but you need not be an expert.

This tutorial consists of a brief introduction to the JPEG standard, compression techniques, explanation of the problems encountered and how they are solved. At the end you can download an example source code package that implements simple JPEG display.

The source code consists of one C language file and a makefile. The example has been compiled using GCC in OpenBSD, and it should compile in GNU/Linux (with the addition of -ldl to the makefile) and other Unix-like systems. Some GNU/Linux systems may need an XFree Development package installed to compile the example. The example works with 15, 16, 24, or 32 bit displays.

JPEG stands for the Joint Photograph Experts Group. They wrote a standard that has become very popular for image compression. The Independant JPEG Group created an open source library for people to use for free in their programs. The file format used by the IJG library is called JFIF.

Compression

JPEG compression is done by generalizing about the image. If the image is 100 pixels wide and 100 pixels high, and there is a row of red color 50x100 pixels, then you can save space in the image by stating that the image has one red row that is 50x100 pixels. JPEG compression is more complicated than this, but it does follow a similar principle. It uses averages of the pixel data and several steps to compress the data. The IJG library compression is lossy, which means that some image data will be lost regardless of quality settings. JPEG compression algorithms were designed for real life images, so some things such as text may appear blurry in an image. There are libraries that deal with lossless JPEG compression, but this document does not cover them, and they are not generally used by the general public.

The XPM image format uses human readable compression, which uses a method similar to above.


The header and one line of the image above represented in XPM format would look like this:
/* XPM */
static char * 50x100_xpm[] = {
"100 100 3 1",
"       c None",
".      c #FF0000",
"+      c #FFFFFF",
"..................................................+++++\
+++++++++++++++++++++++++++++++++++++++++++++",

The XPM above uses characters to represent a certain hexadecimal color. Hexadecimal coloring uses an RGB pattern. FF is the highest number of red, followed by 0000 which means 00 green and 00 blue. FF would be like 255 in a typical paint program, such as the GIMP. The line of . and + represent each pixel. So, because the first pixel is . it should be red. The XPM format allows the user to also use letters instead of . and +.

A JPEG JFIF file is made up of a header which identifies the height, width, and depth of the image. The image may also store a comment, which is usually the name of the program that created it. JFIF is the file format created by the Independant JPEG Group.

X Images

X was designed for a variety of display types -- from black-and-white to 32 bits (8 per channel) of color or more. Xlib doesn't contain code for reading JPEG files, or other compressed formats. Instead it provides an API that allows displaying RGB data in a raw format. The main functions for image display are XCreateImage and XPutImage. It should be noted that XCreateImage makes an image with the byte-order of the X server, which may not be what you want (it isn't for what we are doing).

Dealing with Different Display Depths

With the various display depths we need a way to dither or translate image data, otherwise the image would not appear to have the correct color. A JPEG is generally in 24 bit format. 8 bits are given to each primary color. There are two ways (possibly more) to convert a 24 bit format for a 15 or 16 bit display. Some people like to use bitwise shifting to reduce or increase the value of a color. I on the otherhand do not. X provides a structure called a Visual, which contains a red_mask, green_mask, and blue_mask. The way a 24 bit image can be translated to say a 16 is to first make a ratio:

Visual *vis;
double rRatio, gRatio, bRatio;
...
vis = DefaultVisual (dis, screen);
...
rRatio = vis->red_mask / 255.0;
That should give us an accurate ratio for moving a 24 bit image to whatever the maximum red value is for the display. You may wonder why 255.0 was chosen. The maximum value that 8 bits can store is 255 (think (2 to the power of 8) - 1). The .0 is added to make C operate in terms of a floating point, otherwise the ratio would not be accurate. The next step is to translate the red for a pixel. For example:
unsigned int r, g, b;

r = (buf[i] * rRatio);
At this point you might think we are through, but we aren't. On most systems this will result in some inaccuracy because of limitations of floating point (AIUI). We need a way to only set the bits specified in the red_mask. We can do this by using a bitwise and (&) which will return a value where only the two sets of bits match. For example:
r &= vis->red_mask;
Now we should have a valid red value for this display. We can do this with all the data that the JPEG library decodes for us. The principal is the same with green and blue. If we are working with a 15 or 16 bit display then a u_int16_t type is good for storing the RGB date that is translated. This can be done like so:
newBuf[outIndex] = r | g | b;
For 24 or 32 bit images a u_int32_t type works well.

Creating an XImage

XCreateImage is used to give X the information it needs to know what to do with the array of bytes translated earlier. Most of the arguments can be understood from the man page for XCreateImage. For most images a ZPixmap type is needed. The bitmap_pad argument should be 16 for 15 and 16 bit displays, and 32 for displays >= 24. For example:

img = XCreateImage (dis,
	CopyFromParent, depth,
	ZPixmap, 0,
	(char *) newBuf,
	width, height,
	16, 0
);

One peculiar aspect of XCreateImage is that the structure it creates has the X server's native byte-order. This can be problematic when XPutImage is called, because if the bytes we send are in another format conversion will not be done. To fix this problem we can test for the byte-order. For example:

int get_byte_order (void) {
	union {
		char c[sizeof(short)];
		short s;
	} order;

	order.s = 1;

	if ((1 == order.c[0])) {
		return LSBFirst;
	} else {
		return MSBFirst;
	}
}
Now that we can find the byte order we can do something like:
img->byte_order = LSBFirst;

XPutImage

XPutImage is fairly straight forward. It causes conversion of the client data from LSBFirst to MSBFirst if the X server is running on an MSBFirst machine. For most uses you will want to draw onto a Pixmap using XPutImage and then use XCopyArea to draw onto a Window. By using a Pixmap you can avoid some flicker. There is also an XDBE extension for double-buffering that you could use instead of a Pixmap. The example source code does not draw onto a Pixmap, because a goal of the example was to keep it as simple as possible.

Grayscale JPEG

JPEG images are sometimes grayscale. This means that they sometimes have only one channel or 1 byte for a color. In essence white is 255 or 0xff and black is 0 or 0x00. A grayscale has equal red, green, and blue colors, so we can copy the byte for each grayscale pixel to our red, green, and blue channels in 24 bit format. Then we can perform the translation of pixel values for the display.


Download the Example
The picture below is an image displayed using the example.

Thank you Marcus Meisinger, John Bradley, Srinivas Vanjari, and Kevin B. Kenny for your help.

See also Marcus Meisinger's Xlib Page (may not work)link