Loading Icons

From OSDev Wiki
Jump to navigation Jump to search

Once you have graphical mode, and you have learned how to display pixels and display fonts, it is time to learn how to display icons.

Graphics Modes

Main article: Getting VBE Mode Info
Main article: GOP

If you haven't set up graphics mode, do that first. You can only display icons on pixel based frame buffers.

Decoding Images

To put an icon on screen, all you need to do is iterate on it's pixels, in height lines and width times, copying each pixel one-by-one to the screen. The difficulty is in how to get that pixel array in the first place, because images aren't stored like that on disk.

Windows BMP

Simply put, do NOT use. You'll run into strange problems like some editors save OS/2 bitmap, not Windows bitmap (slightly different, but enough to give you a headache), and that the image is upside down, channel order is not what you expect etc. Unless you're developing on Windows (where the tools only create Windows BMP) using this format is just asking for trouble.

Targa

A much better choice would be Targa format (.tga). It is a very very simple format, used by many game engines (like Quake) too. Essentially you can save uncompressed bitmap images with a 18 bytes header:

typedef struct {
  unsigned char magic1;             // must be zero
  unsigned char colormap:           // must be zero
  unsigned char encoding;           // must be 2
  unsigned short cmaporig, cmaplen; // must be zero
  unsigned char cmapent;            // must be zero
  unsigned short x;                 // must be zero
  unsigned short y;                 // image's height
  unsigned short h;                 // image's height
  unsigned short w;                 // image's width
  unsigned char bpp;                // must be 32
  unsigned char pixeltype;          // must be 40
} __attribute__((packed)) tga_header_t;

This is followed by the data, each pixel on 4 bytes: blue, green, red, alpha channels in order. This simple. Many image editors support this format (like The GIMP), or you can use ImageMagick CLI tool to convert any image format into .tga with a simple command (available in all Linux distributions).

$ convert image.png image.tga

When saving with The GIMP, first convert the image to RGB (Image / Mode / RGB). Then add Alpha channel (Layer / Transparency / Add Alpha Channel). If these options are inactive, that means your image is already RGB or has alpha. Finally choose Export As... and enter a filename ending with ".tga". In the popup, uncheck "RLE compression" and select origin "Top".

If you want to support all TGA options, including palette images and RLE compression, then that's still pretty simple. Just use the following code snippet:

/**
 * Parse TGA format into pixels. Returns NULL or error, otherwise the returned data looks like
 *   ret[0] = width of the image
 *   ret[1] = height of the image
 *   ret[2..] = 32 bit ARGB pixels (blue channel in the least significant byte, alpha channel in the most)
 */
unsigned int *tga_parse(unsigned char *ptr, int size)
{
    unsigned int *data;
    int i, j, k, x, y, w = (ptr[13] << 8) + ptr[12], h = (ptr[15] << 8) + ptr[14], o = (ptr[11] << 8) + ptr[10];
    int m = ((ptr[1]? (ptr[7]>>3)*ptr[5] : 0) + 18);

    if(w<1 || h<1) return NULL;

    data = (unsigned int*)malloc((w*h+2)*sizeof(unsigned int));
    if(!data) return NULL;

    switch(ptr[2]) {
        case 1:
            if(ptr[6]!=0 || ptr[4]!=0 || ptr[3]!=0 || (ptr[7]!=24 && ptr[7]!=32)) { free(data); return NULL; }
            for(y=i=0; y<h; y++) {
                k = ((!o?h-y-1:y)*w);
                for(x=0; x<w; x++) {
                    j = ptr[m + k++]*(ptr[7]>>3) + 18;
                    data[2 + i++] = ((ptr[7]==32?ptr[j+3]:0xFF) << 24) | (ptr[j+2] << 16) | (ptr[j+1] << 8) | ptr[j];
                }
            }
            break;
        case 2:
            if(ptr[5]!=0 || ptr[6]!=0 || ptr[1]!=0 || (ptr[16]!=24 && ptr[16]!=32)) { free(data); return NULL; }
            for(y=i=0; y<h; y++) {
                j = ((!o?h-y-1:y)*w*(ptr[16]>>3));
                for(x=0; x<w; x++) {
                    data[2 + i++] = ((ptr[16]==32?ptr[j+3]:0xFF) << 24) | (ptr[j+2] << 16) | (ptr[j+1] << 8) | ptr[j];
                    j += ptr[16]>>3;
                }
            }
            break;
        case 9:
            if(ptr[6]!=0 || ptr[4]!=0 || ptr[3]!=0 || (ptr[7]!=24 && ptr[7]!=32)) { free(data); return NULL; }
            y = i = 0;
            for(x=0; x<w*h && m<size;) {
                k = ptr[m++];
                if(k > 127) {
                    k -= 127; x += k;
                    j = ptr[m++]*(ptr[7]>>3) + 18;
                    while(k--) {
                        if(!(i%w)) { i=((!o?h-y-1:y)*w); y++; }
                        data[2 + i++] = ((ptr[7]==32?ptr[j+3]:0xFF) << 24) | (ptr[j+2] << 16) | (ptr[j+1] << 8) | ptr[j];
                    }
                } else {
                    k++; x += k;
                    while(k--) {
                        j = ptr[m++]*(ptr[7]>>3) + 18;
                        if(!(i%w)) { i=((!o?h-y-1:y)*w); y++; }
                        data[2 + i++] = ((ptr[7]==32?ptr[j+3]:0xFF) << 24) | (ptr[j+2] << 16) | (ptr[j+1] << 8) | ptr[j];
                    }
                }
            }
            break;
        case 10:
            if(ptr[5]!=0 || ptr[6]!=0 || ptr[1]!=0 || (ptr[16]!=24 && ptr[16]!=32)) { free(data); return NULL; }
            y = i = 0;
            for(x=0; x<w*h && m<size;) {
                k = ptr[m++];
                if(k > 127) {
                    k -= 127; x += k;
                    while(k--) {
                        if(!(i%w)) { i=((!o?h-y-1:y)*w); y++; }
                        data[2 + i++] = ((ptr[16]==32?ptr[m+3]:0xFF) << 24) | (ptr[m+2] << 16) | (ptr[m+1] << 8) | ptr[m];
                    }
                    m += ptr[16]>>3;
                } else {
                    k++; x += k;
                    while(k--) {
                        if(!(i%w)) { i=((!o?h-y-1:y)*w); y++; }
                        data[2 + i++] = ((ptr[16]==32?ptr[m+3]:0xFF) << 24) | (ptr[m+2] << 16) | (ptr[m+1] << 8) | ptr[m];
                        m += ptr[16]>>3;
                    }
                }
            }
            break;
        default:
            free(data); return NULL;
    }
    data[0] = w;
    data[1] = h;
    return data;
}

This will decode the most common TGA variants according to the Targa specification. It will return pixels in a format that's most common for frame buffers.

PNG, JPEG and other "professional" formats

Using these is not a simple thing to do. Definitely more complex than those ca. 80 SLoC above. You could port libpng, libjpeg, etc. but that's not an easy task either. They usually have their dependencies on their own, including setjmp and longjmp, and it is not trivial to use libpng's API for example.

Instead, use stb_image.h. This is a single header file (no library linking needed, just libc), which is easy to use, for example the code to load PNG images looks like this:

#define STBI_IMPLEMENTATION
#define STBI_ONLYPNG
#include <stb_image.h>

/**
 * Parse PNG format into pixels. Returns NULL or error, otherwise the returned data looks like
 *   ret[0] = width of the image
 *   ret[1] = height of the image
 *   ret[2..] = 32 bit ARGB pixels (blue channel in the least significant byte, alpha channel in the most)
 */
unsigned int *png_parse(unsigned char *ptr, int size)
    int i, w, h, f;
    unsigned char *img, *p;
    stbi__context s;
    stbi__result_info ri;
    s.read_from_callbacks = 0;
    s.img_buffer = s.img_buffer_original = ptr;
    s.img_buffer_end = s.img_buffer_original_end = ptr + size;
    ri.bits_per_channel = 8;
    img = p = (unsigned char*)stbi__png_load(&s, (int*)&w, (int*)&h, (int*)&f, 1, &ri);
    data = (unsigned int*)malloc((w*h+2)*sizeof(unsigned int));
    if(!data) { free(img); return NULL; }
    // convert the returned image into frame buffer format
    for(i = 0; i < w * h; i++, p += f)
        switch(f) {
            case 1: data[2 + i] = 0xFF000000 | (p[0] << 16) | (p[0] << 8) | p[0]; break;
            case 2: data[2 + i] = (p[1] << 24) | (p[0] << 16) | (p[0] << 8) | p[0]; break;
            case 3: data[2 + i] = 0xFF000000 | (p[0] << 16) | (p[1] << 8) | p[2]; break;
            case 4: data[2 + i] = (p[3] << 24) | (p[0] << 16) | (p[1] << 8) | p[2]; break;
        }
    free(img);
    data[0] = w;
    data[1] = h;
    return data;
}

One downside is, stb_image.h doesn't return pixels in the same format as the typical frame buffer expects. You must convert those into 32 bit ARGB pixels.

See Also

External Links