Rawheds Tutorial#1:

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
How to code 16bit color graphics

by Rawhed(Andrew Griffiths)/Sensory Overload - 26 April 1999

sfeist@netactive.co.za
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Contents

-=\Foreword
-=\Why 16bpp color?
-=\Background
-=\Setting The Mode
-=\Double Buffers
-=\Putpixel
-=\Getpixel
-=\Copying To Video Memory
-=\How to deal with RGB(Transparency)
-=\Additive
-=\Subtractive
-=\Other Ideas
-=\Closing words
-=\Greets
-=\Contact Me

Foreword

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

Why 16bpp color? (BTW bpp = bits per pixel).

Well its 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 its easy, so why not? ;) Thirdly you can do VERY cool effects very easily. Effects like transparency(multiply layers), fog, RGB lighting, shadows etc. - all all much easier in this mode. Fourthly all your effects(smoothing, dithering, scaling etc) are much easier and you don't have to worry if a color is in the palette, you can just create the color. I've been a 8bpp coders for a long time, but since I've tried 16bpp I've never gone back...(well, once :)

Background

16bit color mode is exactally that. Each pixel is 16bits(2 bytes), as opposed to 1bytes/pixel in 256color modes. So a 320x200 mode will require 128000bytes of space in 16bit 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 extent 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 16bit vmodes.

Another thing is that there is no pallete! Nada. We are now working with RED, GREEN & BLUE. The 16bit 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?

Getting The Mode

To set the mode you need to do a BIOS interrupt. Yes its slow, but we're only doing it once, so its 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 lets have some fun.

Double Buffers

Its 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. Its better to use a 128k buffer, and then every frame flip the 128k buffer to video memory, doing 64k at a time for 2 banks(changing the bank inbetween). 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 2 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 LFB's, 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

Thats 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 128buffer 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 2 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 16bit 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 16bpp? 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
-Rawhed/Sensory Overload
-Mailto:andrew@overload.co.za
-Htpp://www.overload.co.za
-Andrew Griffiths
-South Africa
-05-07-1999