How To Code 16 Bit Color Graphics

Rawhed / Sensory Overload

Ok, I had a request to make this, so I will. I also think that a lot of the South African DOS coders out there are still using mode 13h, and I'd like to show them how cool 16 bpp color mode is. This is not to show how to optimise 16 bpp coding, it just shows the simplest ways of doing things. Optimising is something you can workout later.

Why 16 bits per pixel color?

Well it's simply very cool. Firstly you can have 65536 different colors on the screen at once, compared to 256 colors. So your pictures will look a lot cooler. Secondly it's easy, so why not? Thirdly you can do VERY cool effects very easily. Effects like transparency (multiply layers), fog, RGB lighting, shadows etc. are all much easier in this mode. Fourthly effects like smoothing, dithering, scaling etc. are much easier as well and you don't have to worry if a color is in the palette, you can just create the color. I've been a 8 bpp coder for a long time, but since I tried 16 bpp I've never gone back... (well, once).

Background

16 bit color mode is exactly that. Each pixel is 16 bits (2 bytes), as opposed to 1 byte/pixel in 256 color modes. So a 320x200 mode will require 128000 bytes of space in 16 bit color mode, compared to the normal 64000, because we have to write twice as many bytes - yet there are still 64000 pixels. I'm going to be dealing with 320x200x16bit, as I just want to introduce this concept, then you can easily extend it to other resolutions. I suggest you read a good doc on the VESA spec, as it has a lot of very cool info, like hardware scrolling, all the videomodes, how to get videocard info, etc. I'll just give you the skeleton to play with to get you started in 16 bit vmodes.

Another thing is that there is no palette! Nada. We are now working with RED, GREEN and BLUE. The 16 bit number which is each pixel is structured like this (in binary):

                FEDCBA9876543210   - bit position
                rrrrrggggggbbbbb   - pixel

The 1st 5 bits are for how much BLUE the pixel has, the next 6 bits are for how much GREEN the pixel has, and the next 5 bits are for how much RED the pixel has. So remember that 5bits can be from 0-31, and 6bits from 0-63. So green has more of a range than red and blue. Which is cool, because of all the colors, green is the one the human eye is most sensitive to. So for example:

                FEDCBA9876543210   - bit position
                0000000000000000   - pixel        = black

                FEDCBA9876543210   - bit position
                1111111111111111   - pixel        = white

                FEDCBA9876543210   - bit position
                0000000000011111   - pixel        = red

                FEDCBA9876543210   - bit position
                0000011111100000   - pixel        = green

                FEDCBA9876543210   - bit position
                1111100000000000   - pixel        = blue

                FEDCBA9876543210   - bit position
                1111100000011111   - pixel        = purple

Get it?

Setting The Mode

To set the mode you need to do a BIOS interrupt. Yes it's slow, but we're only doing it once, so it's fine. You can read up on how to do more advanced things like setting up a linear frame buffer by yourself.

        mov ax,4f02       ;the "setmode" function (hex)
        mov bx,10e        ;the video mode to set (hex)
        int 10h

4f02h is the setmode function. Consult a cool VESA doc for all the other functions. 10e is the video mode number. Just like 13h is the mode number for 320x200x8bit, 10e is for 320x200x16bit. Ok, so now you are in 320x200x16bit. Now let's have some fun.

Double Buffers

It's basically essential to use a double buffer. Firstly for speed (writing to video memory is slooow), and also so that the viewer only sees the completed picture. Another very good reason is that if you aren't using LFB (linear frame buffer), you will have to deal with page (64k) boundaries. It's better to use a 128k buffer, and then every frame flip the 128k buffer to video memory, doing 64k at a time for two banks (changing the bank in between). Although I would suggest that you figure out LFB, it makes things much easier.

Putpixel

Just like a normal mode13h putpixel, except that you must remember two things when finding the correct pointer address from the X/Y coordinates.

1. Each pixel is 16bits, not 8bits, so you need to add X*2 (or add X twice).

2. Although the width of the screen is 320 pixels, you need to multiply by 640 because of the number of bytes.

        mov ebx,[X]
        shl ebx,1         ;X*2
        mov edi,[Y]
        mov edx,edi
        shl edi,9
        shl edx,7         ;Y*640
        add edi,edx
        add edi,ebx       ;edi=(x*2)+(y*640)
        add edi,[address] ;edi=edi+pointer to vbuffer
        mov ax,[color]    ;ax=16bit color value
        mov [edi],ax      ;write it!

Getpixel

Getpixel is VERY similar:

        mov ebx,[X]
        shl ebx,1         ;X*2
        mov edi,[Y]
        mov edx,edi
        shl edi,9
        shl edx,7         ;Y*640
        add edi,edx
        add edi,ebx       ;edi=(x*2)+(y*640)
        add edi,[address] ;edi=edi+pointer to vbuffer
        mov ax,[edi],ax   ;get it!
        mov [color],ax    ;ax=16bit color value

Copying To Video Memory

You see.... if you aren't using a LFB it gets complicated (well not too complex). But since I won't get into LFBs, I'll show you a simple way quickly. Since video memory is by default (on most vcards) divided into chunks of 64k, you can only write 64k. So you can only write half the screen in 320x200x16bit mode. What we have to do is this:

1. Switch to bank0
2. Copy 1st 64k
3. Switch to bank1
4. Copy 2nd 64k

That's it. Except that 128k doesn't equal 320x200x2. So 1st we write 64k (65536 bytes) and then in bank 1 we write 128000-65536=62464, which you can see:

                        ;1st 64k
        mov ax,4f05h    ;VESA bank set function
        mov bx,0
        mov dx,0        ;bank number(0)
        int 10h
        mov edi,0a0000h ;we want to write to video memory
        mov esi,[where] ;from this address
        mov ecx,16384   ;this ammount(65536/4)
        rep movsd       ;write loop, 4bytes/write

                        ;2nd 64k
        mov ax, 4f05h   ;VESA bank set function
        mov bx,0
        mov dx,1        ;bank number(1)
        int 10h
        mov edi,0a0000h ;we want to write to video memory
        mov esi,[where] ;from this address
        add esi,65536   ;plus this ammount
        mov ecx,15616   ;this ammount(128000-65536)/4
        rep movsd       ;write loop, 4bytes/write

That will copy your 128 buffer to the screen so you can see it.

How To Deal With RGB (Transparency)

One of the most important things to be able to do (initially), is to be able to extract the RGB values from the 16bit color value. But one must remember that not ALL videocards handle 16bit modes the same. Some structure the pixels:

         RGB16 rrrrrggggggbbbbb (most common)
         BGR16 bbbbbggggggrrrrr (backwards)
         RGB15 rrrrr0gggggbbbbb (everything is 5bits)

After reading your VESA spec, you will see how to get info from VESA calls about which type your card is using. I'll just work with RGB16 for now.

You have 5bits:RED 6bits:GREEN and 5bits:BLUE, you must extract the RGB values using masks and shifting.

Some of us had to figure all of this out by ourselves, but here it is:

        mov ax,[edi]             ;pixel value
        mov bx,ax                ;backup pixel value
                                 ;Now extract BLUE
        and bx,0000000000011111b ;mask unwanted bits
        mov [blue],bx            ;nab it
                                 ;Now extract GREEN
        mov bx,ax                ;backup pixel value
        and bx,0000011111100000b ;mask unwanted bits
        shr bx,5                 ;shift right
        mov [green],bx           ;nab it
                                 ;Now extract RED
        mov bx,ax                ;backup pixel value
        and bx,1111100000000000b ;mask unwanted bits
        shr bx,11                ;shift right
        mov [red],bx             ;nab it

Now we have red, green & blue in separate variables. Now imagine if for transparency we got the RGB values of two pixels, and did this:

        r=(red1+red2)/2;
        g=(green1+green2)/2;
        b=(blue1+blue1)/2;

And then wrote the pixel (after recompiling the RGB into the color value):

        mov ax,[r]   ;get red
        shl ax,11    ;shift it
        mov bx,[g]   ;get green
        shl bx,5     ;shift it
        add ax,bx    ;add it to pixel
        mov bx,[b]   ;get blue
        add ax,bx    ;add it to pixel
        mov [edi],ax ;write the pixel

So I recompiled the separate RGB values into one 16 bit value again, and then wrote it. So you see how you could to transparency now?

Additive

Very similiarly, what if we did:

        r=(red1+red2);
        g=(green1+green2);
        b=(blue1+blue2);
        if (r>31) r=31;
        if (g>63) g=63;
        if (b>31) b=31;
        writepixel(r,g,b);

Then we would be doing very cool additive drawing, without overflowing. So you could layer sprites on top of each other, and then would combine their color values, until they add up to white. This can be used very effectively in explosion/particle effects.

Subtractive

Very similiarly, what if we did:

        r=(red1-red2);
        g=(green1-green2);
        b=(blue1-blue2);
        if (r<0) r=0;
        if (g<0) g=0;
        if (b<0) b=0;
        writepixel(r,g,b);

Other Ideas

I can't go through all the possible uses. But I will just put a few thoughts into your mind. How easy would crossfading be using 16 bpp? How about a bluring effect? How about a function that makes the image negative colors? How about a function which changes the percentage of RGB in an images? How about cool transparent texturemapped 3d objects? How about lighting images using additive mapping of a lightmap? How about shadows? If you are scaling/mapping an image to a differnt size/shape - won't you be able to interpolate the colors, and add new colors to add extra detail and eliminate blockyness when zooming in close? Won't anti-aliasing be easy?

Closing Words

Hehe, I hope you understood this tutorial, and that it will help you with your coding. I might write other tutorials if people request them. I have much to learn about coding, but I am always happy to share what I do know with others. Happy coding!

Greets

The SA demoscene!
The demoscene.
Anybody who has every released source.
Anybody who has every released a tutorial.
Saurax, Rawnerve, and all the old SO members(contact me!)
Viper, Maverick, Caz, Neuron, Riot, Deadpoet, Jahya.
Celestial & workers-with
People who are hosting the sademoscene site! Thanks!
All my friends far away!

Contact Me

Things might change, so these are my email addies in order of stability. If you try one and nothing happens, try another.

andrew@overload.co.za sfeist@netactive.co.za rawhed@hotmail.com

Visit the SA demoscene site at: http://www.surf.to/demos Enter Optimise99!!!

                                                  -Rawhed/Sensory Overload
                                                  -Mailto:andrew@overload.co.za
                                                  -Http://www.overload.co.za
                                                  -Andrew Griffiths
                                                  -South Africa
                                                  -05-07-1999