X11 Pixmaps and Bitmaps
Learn X11 XPM and XBM image formats: file structure, creation with ImageMagick, loading via Xlib/libxpm in C, viewing tools, and when to migrate to PNG or SVG.
Before you start
- ▸A running X11 session or XWayland (for Xlib/libxpm code examples)
- ▸GCC and standard build tools (build-essential / base-devel / Development Tools)
- ▸ImageMagick 6 or 7 installed
- ▸libxpm and its development headers installed
X11 pixmaps (XPM) and bitmaps (XBM) are legacy image formats baked into the X Window System from its earliest days. XBM is a plain-text, two-colour (1-bit) format stored as a C header; XPM extends that idea to full colour using a text-based colour-symbol table. Both still appear in window manager themes, icon sets, cursor definitions, and low-level Xlib applications — so understanding them is practical even on a modern Wayland host running XWayland. This guide covers the on-disk format, the tooling to create and convert these images, programmatic use through Xlib and libxpm, and how the formats fit (or don't) into a modern GTK/Qt pipeline.
XBM Format: Structure and Creation
An XBM file is literally a C source fragment. Open any .xbm and you will see this pattern:
cat /usr/share/pixmaps/xterm-color_48x48.xbm 2>/dev/null || \
find /usr/share -name '*.xbm' | head -3
A minimal hand-crafted 8×8 checkerboard XBM looks like this (save it as check.xbm):
cat > check.xbm <<'EOF'
#define check_width 8
#define check_height 8
static unsigned char check_bits[] = {
0xAA, 0x55, 0xAA, 0x55,
0xAA, 0x55, 0xAA, 0x55 };
EOF
Each byte encodes eight pixels, LSB first. Bit 0 = foreground (drawn in the widget's foreground colour); bit 1 = background. The format has no colour data — colours come entirely from the X11 graphics context at draw time.
Converting Images to XBM
ImageMagick's convert (legacy) or magick (v7+) writes XBM directly:
# ImageMagick 7
magick input.png -threshold 50% -monochrome output.xbm
# ImageMagick 6 (still default on Ubuntu 22.04 and Debian 12)
convert input.png -threshold 50% -monochrome output.xbm
The -threshold 50% step is important: XBM is 1-bit, so continuous-tone images need thresholding or dithering before conversion.
# Floyd-Steinberg dithering — usually better for photos
magick input.png -dither FloydSteinberg -monochrome output.xbm
XPM Format: Structure and Creation
XPM (X PixMap) version 3 is also a C source fragment. Each pixel is represented by one or more characters that map to a colour definition. The structure is:
cat > smiley.xpm <<'EOF'
/* XPM */
static char *smiley_xpm[] = {
/* columns rows colors chars-per-pixel */
"16 16 3 1",
" c None",
". c #FFD700",
"X c #000000",
/* pixels */
" ........ ",
" ............ ",
" .............. ",
".......XX.......",
"......XXXX......",
".......XX.......",
"................",
"................",
".XX..........XX.",
".XXX........XXX.",
"..XXXX....XXXX..",
"....XXXXXXXX....",
" .............. ",
" ............ ",
" ........ ",
" "
};
EOF
The first string in the array is the header: width, height, number of colours, characters per pixel. Each subsequent colour line maps a symbol to an X11 colour name or hex value. None means transparent (honoured by Xlib when the drawable has a shape mask).
Converting Images to XPM
# Limit colour count for sane file sizes — 64 colours is typical for icons
magick input.png -colors 64 output.xpm
# Preserve transparency
magick input.png -background none -colors 64 output.xpm
GIMP also exports XPM natively: File → Export As → *.xpm. For scripted batch work, ImageMagick is the practical choice.
Installing Required Tools
Debian / Ubuntu
sudo apt install imagemagick libxpm-dev libxpm4 x11-apps sxiv
Fedora / RHEL 9+ / Rocky
sudo dnf install ImageMagick libXpm libXpm-devel xorg-x11-apps
Arch Linux
sudo pacman -S imagemagick libxpm xorg-xsetroot
Loading Pixmaps with Xlib and libxpm
For C programs that draw directly with Xlib, libxpm provides the canonical API. The following minimal example loads an XPM file onto the root window — useful for understanding what window managers do internally.
cat > show_xpm.c <<'EOF'
#include <X11/Xlib.h>
#include <X11/xpm.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
if (argc < 2) { fprintf(stderr, "usage: %s file.xpm\n", argv[0]); return 1; }
Display *dpy = XOpenDisplay(NULL);
if (!dpy) { fprintf(stderr, "Cannot open display\n"); return 1; }
int screen = DefaultScreen(dpy);
Window root = RootWindow(dpy, screen);
Pixmap pix, mask;
XpmAttributes attrs = {0};
int rc = XpmReadFileToPixmap(dpy, root, argv[1], &pix, &mask, &attrs);
if (rc != XpmSuccess) {
fprintf(stderr, "XpmReadFileToPixmap error %d\n", rc);
XCloseDisplay(dpy);
return 1;
}
printf("Loaded %ux%u pixmap (depth %u)\n",
attrs.width, attrs.height, attrs.depth);
/* Set as root background for demonstration */
XSetWindowBackgroundPixmap(dpy, root, pix);
XClearWindow(dpy, root);
XFlush(dpy);
XpmFreeAttributes(&attrs);
/* Pixmap lives in X server memory; freeing it here clears the bg */
/* XFreePixmap(dpy, pix); */
XCloseDisplay(dpy);
return 0;
}
EOF
gcc -O2 -Wall -o show_xpm show_xpm.c $(pkg-config --cflags --libs x11 xpm)
./show_xpm smiley.xpm
This only works under X11 or XWayland. On a native Wayland compositor, DISPLAY is unset unless XWayland is running. Check with echo $DISPLAY; if blank, start an XWayland session or test inside an X11 terminal emulator.
Loading XBM in Xlib
XBM support is built directly into Xlib — no extra library needed:
cat > show_xbm.c <<'EOF'
#include <X11/Xlib.h>
#include <stdio.h>
int main(void) {
Display *dpy = XOpenDisplay(NULL);
int screen = DefaultScreen(dpy);
Window root = RootWindow(dpy, screen);
unsigned int w, h;
Pixmap bm;
int rc = XReadBitmapFile(dpy, root, "check.xbm", &w, &h, &bm, NULL, NULL);
if (rc != BitmapSuccess) { fprintf(stderr, "Failed: %d\n", rc); return 1; }
printf("Bitmap %ux%u loaded into server pixmap ID %lu\n", w, h, bm);
XFreePixmap(dpy, bm);
XCloseDisplay(dpy);
return 0;
}
EOF
gcc -O2 -Wall -o show_xbm show_xbm.c -lX11
Viewing XPM and XBM Files
Most image viewers handle XPM; XBM support is spottier. The most reliable cross-distro tools:
# ImageMagick display utility (X11 only)
display smiley.xpm
display check.xbm
# Convert to PNG first for Wayland-native viewers
magick smiley.xpm smiley.png && eog smiley.png
# eog, feh, sxiv all handle XPM directly on X11
feh smiley.xpm
Modern Alternatives and When to Use Them
XPM and XBM predate PNG, SVG, and virtually every modern image format. Here is when each format is still the right choice versus when to reach for something else:
- XBM — still the correct format for X11 cursor masks, window shape masks (
XShapeCombineMask), and stipple patterns in Xlib GCs. - XPM — survives in older window manager themes (Motif, older Openbox themes, xterm icon resources). New projects should use PNG or SVG.
- PNG via GDK / Cairo — prefer this for any GTK application.
gdk_pixbuf_new_from_file()reads PNG, JPEG, WebP, and more. - SVG via librsvg — the right choice for scalable icons at arbitrary DPI. GNOME, KDE, and most modern desktops use SVG for system icons.
Converting an XPM icon set to PNG for a modern app:
for f in icons/*.xpm; do
magick "$f" -background none "${f%.xpm}.png"
done
Verification
Confirm your XPM is valid before embedding it in a project:
# ImageMagick will error out on malformed XPM
magick identify smiley.xpm
# Expected output (will vary): smiley.xpm XPM 16x16 16x16+0+0 8-bit sRGB ...
# Validate C compilation of the embedded XPM
gcc -fsyntax-only -x c smiley.xpm && echo "XPM syntax OK"
Because XPM is valid C, compiling it as a source file is the definitive syntax check.
Troubleshooting
XpmReadFileToPixmapreturnsXpmColorFailed(-3) — the XPM references colour names that are not in the X colour database (/usr/share/X11/rgb.txt). Replace named colours with hex values (#RRGGBB).- Transparent areas appear black — you allocated a Pixmap with depth > 1 but no shape mask. Pass the
maskpointer toXpmReadFileToPixmapand apply it withXShapeCombineMaskif your window supports the SHAPE extension. displayor./show_xpmcrashes with "Cannot open display" — noDISPLAYenvironment variable. Under Wayland, runexport DISPLAY=:0only if XWayland is active; check withps aux | grep Xwayland.- ImageMagick refuses to read XPM (policy error) — Debian/Ubuntu ship a restrictive
/etc/ImageMagick-6/policy.xml. Edit the policy or use the-readflag:magick -read smiley.xpm out.png. On Ubuntu 24.04 and later, check/etc/ImageMagick-7/policy.xml. - Colours look wrong after conversion from PNG — add
-colorspace sRGBbefore the output filename to force correct colour space handling in ImageMagick.
Frequently asked questions
- Can I use XPM files on a Wayland compositor without XWayland?
- Not directly through Xlib — XPM loading via libxpm requires an X display connection. Convert XPM to PNG first and use a Wayland-native image API such as GDK Pixbuf or Cairo's PNG surface.
- Why does my XPM appear as a black rectangle instead of showing transparency?
- The 'None' colour in XPM signals transparency, but Xlib only honours it when you pass a mask Pixmap to XpmReadFileToPixmap and apply it to your window using XShapeCombineMask. Without the shape mask, transparent pixels default to black.
- What is the maximum number of colours an XPM file can contain?
- The format has no hard limit, but practical use caps out well below 256 colours because the file size grows linearly with colour count and the number of characters-per-pixel increases. For photographic images, use PNG instead.
- Is XBM the same as the BMP format used on Windows?
- No. XBM is a plain-text C header encoding a 1-bit monochrome bitmap specific to X11. Windows BMP is a binary raster format supporting multiple bit depths. They share no structural similarity.
- Can GTK applications load XPM icons directly?
- Yes — gdk-pixbuf includes an XPM loader, so gdk_pixbuf_new_from_file() will open XPM files. However, the loader is sometimes compiled as an optional module; verify with gdk-pixbuf-query-loaders | grep xpm.
Related guides
Bash Loops: for, while and until
Learn all three Bash loop types — for, while, and until — with practical, copy-paste examples covering file iteration, counting, polling, and safe line reading.
Bash Scripting for Beginners
Learn Bash scripting from scratch: shebang lines, variables, conditionals, loops, and arguments, plus a real backup script to tie it all together.
C Pointers Explained
Understand C pointers from first principles: addresses, dereferencing, pointer arithmetic, arrays, common bugs like null dereferences and dangling pointers, and how to use ASan and Valgrind.
A C Programming Tutorial for Linux
Learn C on Linux from hello-world through gcc flags, header files, multi-file projects, make, and the standard library — with real commands and examples.