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 does not require any previous knowledge of Xlib or of the Independant JPEG Group's library.
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.
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.
/* 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 was designed for a variety of display types -- from black-and-white to 32 bits 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).
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.
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 is 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 MEM_LSBFirst;
} else {
return MEM_MSBFirst;
}
}
Now that we can find the byte-order we can do something like:
img->byte_order = LSBFirst; img->bitmap_bit_order = LSBFirst; [check if I am checking the bit_order correctly]
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 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. The example source code does not draw into a Pixmap, because a goal of the example was to keep it as simple as possible.
The picture below is an image displayed using the example.
Thank you Marcus Meisinger, John Bradley, and Srinivas Vanjari for your help.
See also Marcus Meisinger's Xlib Page link