FreeBasic offers many high level commands (get, put, draw...) to manage graphics: these commands, anyway, are general-purpose, and so are not optimized for a specific task: speed has been sacrificed for versatility.
But we don't have to use them: we can bypass built-in commands, and directly use video memory: it's less intuitive, but much faster.
A graphic page is stored in computer memory as a sequence of bits (like everything else). In FreeBasic, for every pixel a different amount of memory can be used: 1 byte (it means 256 possible colors for each pixel), or 4 bytes (one byte for blue level, one for green and one for red; the fourth byte is the alpha, or transparency, level).
Using 1 byte for each pixel, 256 colors can be used: but which colors? You can define all of them, using the PALETTE command: each of 256 colors will index to the correct color in the palette; that's what is meant when we talk about indexed colors.
Using 4 bytes for each pixel, we can have 256 levels of blue, 256 levels of green, 256 levels of red, and that means 16777216 different colors! Our eyes cannot even discern between all of them... but wait: we used three bytes (24 bits), why do we need a fourth byte (8 more bits, so 24+8=32 bits)? Because the processor uses 32-bits registers, so it can work faster with 32-bit data chunks than with 24-bit data chunks. We will revisit this later.
First, we need to enable graphic mode:
screenRes 800, 600, 32
Also, we need to access video memory; to do so, we need a pointer: computer memory (including video memory) is organized in cells: every cell has a number, called an address (if you know it, you can read or write to/from a specific cell); a pointer is a variable containing the address.
Since we are talking about bytes, we'll use an (unsigned) byte pointer:
dim as ubyte ptr target
Also, we need to point to video memory: but... what's its address? We can find out using the screenPtr keyword:
target = screenPtr
Remember, to access the memory cell (in this case, the value of the pixel) of the pointer, we can use the “*” prefix:
*target = 255
...to change the address (for example, to go to the next address), we need to increment or decrement the pointer itself:
target = target + 1
target += 1
Anyway, before messing with video memory, we must ensure that nobody else (no other function) will do the same. So, we must disable all other video functions -- refresh and so on: we have to lock the screen. The command is:
When we finish, we'll use:
screenRes 800,600,32 dim as ubyte ptr target = screenPtr screenLock for i as integer = 0 to 99999 *target = 255 target += 1 next i screenUnlock sleep
We will see that the upper section of the screen has become white.
But we want to make the screen fully white: how many bytes do we need to change?
Well, the screen (in our example) is 800 * 600 pixels, and every pixel requires 4 bytes (do you remember? One for blue, one for green, one for red, one for the alpha level.)
Let's try again:
screenRes 800,600,32 dim as ubyte ptr target = screenPtr screenLock for i as integer = 0 to 800*600*4-1 *target = 255 target += 1 next i screenUnlock sleep
Now it is a completely white screen! If we replace “255” with a lower value, we will get a darker screen: a value of 128 will lead to a gray screen, a value of 64 to a darker gray, and a value of 0 will result in a black screen.
But now, let's try to mess with single colors: we'll need to manage a chunk of bytes to do that. So, let's change the FOR loop by removing the “* 4”, and let's increment the pointer by one pixel at time (4 bytes):
screenRes 800,600,32 dim as ubyte ptr target = screenPtr screenLock for i as integer = 0 to 800*600-1 *target = 255 target += 4 next i screenUnlock sleep
What happened? The screen is blue!
In fact, we set only the first byte of every chunk (then, by incrementing the pointer by 4, we go to the next chunk). And the first byte of a chunk is the blue level.
Let's try to mess with other bytes, too:
screenRes 800,600,32 dim as ubyte ptr target = screenPtr screenLock for i as integer = 0 to 800*600-1 *target = 255 ' Blue level. *(target+1) = 255 ' Green level. *(target+2) = 255 ' Red level. target += 4 next i screenUnlock sleep
Now, we are back at a white screen, but now let's try to vary color levels!
Of course, we can also use pointers to read color levels:
screenRes 800,600,32 dim as ubyte ptr target = screenPtr screenLock for i as integer = 0 to 800*600-1 *target = *target ' Blue level. *(target+1) = *(target+1) ' Green level. *(target+2) = *(target+2) ' Red level. target += 4 next i screenUnlock sleep
This won't affect the image (every color value is simply replaced with... itself!). But try something like:*target = (*target) / 2 ' Blue level.
..after drawing something to the screen: the blue level will be lowered: the image will be darker and will have a yellowish hue.
This example will provide a simple fade effect:
screenRes 800,600,32 print string (7500,"#") dim as ubyte ptr target screenLock for a as integer = 0 to 7 target = screenPtr for i as integer = 0 to 800*600-1 *target = (*(target)+1)/2-1 ' Blue level. *(target+1) = (*(target+1)+1)/2-1 ' Green level. *(target+2) = (*(target+2)+1)/2-1 ' Red level. target += 4 next i screenUnlock sleep next a sleep
Try to “filter” an image: you can remove a single color, or set that color to the maximum...
Ok, now let's say that we do not want to mess with single colors, but to handle pixels, instead: since a pixel is made of 4 bytes, you'd have to handle a chunk of 4 bytes. The variable types integer and unsigned integer (uinteger) use exactly 4 bytes!
So, we need to use:dim as uinteger ptr target = screenPtr
The video pointer will now allow access to a whole pixel chunk (and, if we increment the pointer, we will move to the next chunk) First, the base (which does nothing, yet):
screenRes 800,600,32 dim as uinteger ptr target = screenPtr screenLock for i as integer = 0 to 800*600-1 *target = *target target += 1 next i screenUnlock sleep
We can also use unsigned integers to move pixels: in fact, we can copy a pixel to another, by using *target = *(target + x)
How do we implement this? Here is an example (smooth scrolling):
screenRes 800,600,32 print string(7500, "!") dim as uinteger ptr target, source screenLock for a as integer = 0 to 99 target = screenPtr source = target + 1 for i as integer = 0 to 800*600-1 *target = *source if i <> 800*600-2 then source +=1 else source -= (800*600-1) target +=1 next i screenUnlock sleep 10 next a sleep
The screen will scroll to left. If, instead of adding 1 to target (next column), we add 800 (a whole row), then we will go to next row:
screenRes 800,600,32 print string(7500, "!") dim as uinteger ptr target, source screenLock for a as integer = 0 to 99 target = screenPtr source = target + 1 for i as integer = 0 to 800*600-1 *target = *source if i <> 800*600-801 then source +=1 else source -= (800*600-800) target +=1 next i screenUnlock sleep 10 next a sleep
The screen will scroll up: notice that, in both examples, to avoid reading outside the video buffer, we must rollover the source value to keep it less than (screenPtr + 800*600).
We can also scroll in opposite directions, but we have to go backward (decreasing pointer, instead of increasing it): otherwise, we will overwrite pixels that we haven't copied yet.
Of course, going backward means also that we need to start from last pixel: so, ScreenPointer plus number of video bytes, that is equal to number of pixels multiplied to the number of bytes per pixel (that is 4)
Of course, we can also have more than one pointer, and use them to swap pixels (it's like swapping every other variable: we only need a temporary variable to store value): this example show how to use this trick to turn the screen upside-down:
screenRes 800,600,32 print string(7500,"a") dim as uinteger ptr target1=screenptr dim as uinteger ptr target2=screenptr+(4*800*600) dim tmp as uinteger screenLock for i as integer = 0 to 800*600/2 - 1 tmp=*target1 *target1=*target2 *target2=tmp target1+=1 target2-=1 next i screenUnlock sleep
And this concludes our little tutorial: now, have fun with fast graphics!