Task 16  Task 16: Advanced Graphics  Task 16


Task 8 introduced you to the basic graphics commands that BASIC programmers have been using since the 1980's. It's now time to explore the new feature rich graphics command set offered by QB64 and truly unlock the power of today's BASIC. This task is rather lengthy because of the numerous code examples included so take your time digesting the vast amount of information contained in it.

- SCREEN and _NEWIMAGE() -

In Task 8 you learned how the SCREEN statement can be used to open a predefined BASIC screen with a fixed set of colors available. Unfortunately, the highest resolution offered is a paultry 640x480 resolution with up to 16 colors on the screen at one time, or SCREEN 12. To get 256 colors at a time you need to downgrade to a resolution of 320x200, or SCREEN 13. On today's high resolution HD monitors a screen this size isn't much bigger than a large Windows icon! Playing a game in a window this size would be extremely frustrating and almost impossible, forcing gamer's to uninstall your game and forget it even existed.

Fortunately the SCREEN statement has a new weapon to wield when creating screens called _NEWIMAGE(). The _NEWIMAGE function is used to create a new blank image in memory that can be called upon by the use of an image handle number, much the same way sounds are loaded into memory and called upon by sound handle numbers.


DIM GameScreen& ' image handle

GameScreen& = _NEWIMAGE(800, 600, 32) ' create new image in RAM

The code above creates a long integer variable to hold the handle number of a new image being created. The _NEWIMAGE function in the line of code above created an image in memory that is 800 pixels wide by 600 pixels high and capable of 32 bit color (16 million plus colors!). Now all that is left to do is apply this image to the screen using the SCREEN statement:

DIM GameScreen& ' image handle

GameScreen& = _NEWIMAGE(800, 600, 32) ' create new image in RAM
SCREEN GameScreen& '                    apply image to screen
CLS '                                   clear screen
PRINT '                                 show user our excitement
PRINT " An 800x600 screen!"

In fact, SCREEN and _NEWIMAGE can be combined on the same line of code to produce identical results:

SCREEN _NEWIMAGE(800, 600, 32) ' create new image and apply to screen
CLS '                            clear screen
PRINT '                          show user our excitement
PRINT " An 800x600 screen!"

It's always best to follow the SCREEN statement with a CLS statement on the next line to clear the screen. By default all screens created have a transparency layer making solid black a transparent color. This can cause undesirable results when working with transparent images (which will be discussed soon). The CLS statement removes this default transparency layer.

The _NEWIMAGE function has the following features available:

handle& = _NEWIMAGE(width&, height&, mode%)

handle& - the long integer handle value used to refer to this image by QB64 graphics statements
width&  - the width in pixels of the image (0 through width& - 1)
height& - the height in pixels of the image (0 through height& - 1)
mode%   - the number of colors available to place on the image

You must remember that width& and height& refers to the total pixels both horizontally and vertically, therefore an 800x600 image has 0 - 799 pixels in width and 0 - 599 pixels in height. Don't forget about the nagging fact that computers always start at 0 (zero).

Mode% can be 0, 1, 2, 7, 8, 9, 10, 11, 12, 13, 256 and 32. Modes 0 through 13 replicate the color offerings of the original SCREEN modes as learned in Task 8. This is handy when taking BASIC source code written before QB64 was available and upgrading it with a _NEWIMAGE screen but keeping the color compatibility intact. Mode 256 means that you wish to have 256 colors (8 bit) available to the image and mode 32 means 32 bit color, or more than 16 million colors available to the image.

Let's take the psychedelic code from Task 8 and rework it using a 32 bit color screen and 16 million colors. In Task 8 we used a SCREEN 12 statement which set up a 640x480 screen with 16 colors available. Here we are going to use _NEWIMAGE(640, 480, 32) and an updated color function called _RGB32() (which we'll get to in a bit) to see if we can smooth the colors out a bit.

'--------------------------------
'- Variable Declaration Section -
'--------------------------------

DIM BorderColor~& ' circles drawn using this color (unsigned long)
DIM PaintColor~& '  a random color to paint the circle (unsigned long)
DIM Radius% '       the current circle's radius
DIM x%, y% '        the coordinates of the current circle
DIM Xdir% '         the horizontal direction of the circle
DIM Ydir% '         the vertical direction of the circle
DIM Rdir% '         add/subtract to size of circle radius
DIM Red% '          red color component
DIM Green% '        green color component
DIM Blue% '         blue color component

'----------------------------
'- Main Program Begins Here -
'----------------------------

SCREEN _NEWIMAGE(640, 480, 32) '                     enter a graphics screen
RANDOMIZE TIMER '                                    seed random number generator
x% = 319 '                                           start circles in center of screen
y% = 239
Red% = 2 '                                           set color component start values
Green% = 2
Blue% = 2
DO '                                                 start a loop
    Xdir% = INT(RND(1) * 2) - INT(RND(1) * 2) '      random number from -1 to 1
    Ydir% = INT(RND(1) * 2) - INT(RND(1) * 2) '      random number from -1 to 1
LOOP UNTIL Xdir% <> 0 AND Ydir% <> 0 '               end loop when neither is 0
Radius% = 100 '                                      start with radius of 100
BorderColor~& = _RGB32(0, 0, 1) '                    circles always drawn this color
Rdir% = -1 '                                         radius will begin shrinking
DO '                                                 main loop starts here
    PaintColor~& = _RGB32(Red%, Green%, Blue%) '     mix the paint colors
    CIRCLE (x%, y%), Radius%, BorderColor~& '        draw the initial circle
    PAINT (x%, y%), PaintColor~&, BorderColor~& '    paint the circle in
    CIRCLE (x%, y%), Radius%, PaintColor~& '         draw outside of circle same color
    x% = x% + Xdir% '                                move the x coordinate
    y% = y% + Ydir% '                                move the y coordinate
    Red% = Red% + 1 '                                increment red color component
    IF Red% = 256 THEN Red% = 2 '                    keep it wthin limits
    Green% = Green% + 2 '                            increment green color component
    IF Green% = 256 THEN Green% = 2 '                keep it within limits
    Blue% = Blue% + 4 '                              increment blue color component
    IF Blue% = 256 THEN Blue% = 2 '                  keep it within limits
    Radius% = Radius% + Rdir% '                      increase/decrease radius
    IF (x% < Radius%) OR (x% > 639 - Radius%) THEN ' x hit side of screen?
        Xdir% = -Xdir% '                             yes, reverse x direction
    END IF
    IF (y% < Radius%) OR (y% > 479 - Radius%) THEN ' y hit side of screen?
        Ydir% = -Ydir% '                             yes, reverse y direction
    END IF
    IF (Radius% = 5) OR (Radius% = 100) THEN '       radius too big or small?
        Rdir% = -Rdir% '                             yes, reverse radius adder
    END IF
    _DISPLAY '                                       update screen with results
LOOP UNTIL INKEY$ <> "" '                            loop until user presses key
SYSTEM '                                             return to Windows

Smooth
Figure 1 - Smooth and Psychedelic

- _LOADIMAGE() -

QB64's _LOADIMAGE() function loads an image file and places it in RAM with a long integer handle value to reference it.

DIM Sky& ' handle to hold sky image

Sky& = _LOADIMAGE(".\GFX\sky.png", 32) ' load the sky image into RAM
SCREEN Sky& '                            use sky image as screen
SLEEP '                                  wait for a key press

The Sky
Figure 2 - The sky image set as current screen

_LOADIMAGE is a very easy function to use as it only supports one optional parameter:

handle& = _LOADIMAGE(filename$[, mode&])

handle&   - the long integer handle value used to refer to this image by QB64 graphics statements.
filename$ - the name of the file to load from disk.
mode&     - the color mode to load the image in, 256 for 256 color or 32 for 32 bit color (16 million plus colors)

If you do not supply a mode& value the image will take on the current color characteristics of the image, or screen, it is being placed onto to (we'll get into placing images in a bit). Here is an example showing what this means:


DIM Sky& '      handle to hold sky image
DIM Color32& '  handle to hold 32bit color image
DIM Color256& ' handle to hold 256 color image

Color32& = _NEWIMAGE(640, 480, 32) '   create a 16M (32 bit) color image
Color256& = _NEWIMAGE(640, 480, 256) ' create a 256 (8 bit) color image
SCREEN Color32& '                      set screen as 32bit color image
Sky& = _LOADIMAGE(".\GFX\sky.png") '   load the sky image into RAM
_PUTIMAGE (0, 0), Sky& '               place sky image on screen
SLEEP '                                wait for a key stroke
_FREEIMAGE Sky& '                      remove the image from RAM
SCREEN Color256& '                     set screen as 256 color image
Sky& = _LOADIMAGE(".\GFX\sky.png") '   load the sky image into RAM
_PUTIMAGE (0, 0), Sky& '               place sky image on screen
SLEEP '                                wait for a key stroke

32bit vs 256 color
Figure 3 - 32 bit color vs 256 color

It's hard to tell from the program that the colors change, but figure 3 above clearly shows the difference between the two color modes. The 256 color screen uses its pallette of colors to as closely match the 32 bit image's colors as possible, but as you see the difference can be noticable. It's always best to match an image's color setting to the screen or image it is being placed on to avoid this from happening.

_LOADIMAGE supports a wide range of graphics file types to choose from, including BMP, JPG, PNG, GIF, PNM, XPM, XCF, PCX, TIF, LBM, and TGA. However, the only ones really relevant to today's game programmers are JPG, PNG and GIF. BMP files are much too large in file size because they offer no compression. XPM, XCF, PCX, TIF and LBM are very obscure graphic file types with PCX being the oldest amongst them. TGA, although a modern graphics file type, is mainly relegated to animation and video because it's output is geared toward color matching on screens rather than print media.

PNG and JPG, both very popular graphics file types, support 32 bit color and excellent compression for smaller file sizes. They both also support transparency, or a color that can be designated as invisible to allow other graphics to show through (we'll get to that later). However, QB64 only supports transparency with PNG files and not JPG. Because of JPG's compression algorithm many JPGs are "blocky" in appearance which can lead to poor image quality. PNG files on the other hand offer a compression algorithm almost as good as JPG without losing image quality, known as "lossless compression". My first, and usually only, choice in graphics files is PNG because of their small file size, transparency capabilities and high image quality. Many paint and photo programs out today support loading, converting and saving a myriad of graphic file types. A free open source program available for download that rivals the features of Photoshop is The GIMP (which uses XCF as its native graphics file format but supports most of the above mentioned graphics file types).

GIF is a throwback from the late 80's that at one point was all the rage on the Internet for page graphics because they support embedded animation. In the early 2000s it looked as though GIF was finally on it's way out because Flash technology was becoming the web animators first choice. However, GIF is making a comeback as a simple animation vehicle, even though it does not support sound. QB64 does not support the animation characteristics of GIF so only the first frame will get loaded when trying to open an animated GIF (there are ways to load them through QB64 though, if you are curious). Furthermore, GIF only supports up to 256 colors at a time.


- _WIDTH() and _HEIGHT() -

If you are loading images and need to know their dimensions then the _WIDTH() and _HEIGHT() functions will return that information for you.

DIM Sky& '       handle to hold sky image
DIM SkyWidth% '  width of sky image
DIM SkyHeight% ' height of sky image

Sky& = _LOADIMAGE(".\GFX\sky.png", 32) '                  load image into RAM
SkyWidth% = _WIDTH(Sky&) '                                get image width
SkyHeight% = _HEIGHT(Sky&) '                              get image height
SCREEN _NEWIMAGE(SkyWidth%, SkyHeight%, 32) '             creat graphics screen
PRINT '                                                   inform user of results
PRINT " This screen matches the dimensions of sky.png."
PRINT
PRINT " Width  ="; SkyWidth%
PRINT " Height ="; SkyHeight%
SLEEP '                                                   wait for keystroke

The code above is using the width and height information for a graphics file to create a new SCREEN with the same dimensions using _NEWIMAGE. It's often preferable to create a SCREEN in such a way instead of using the actual image for SCREEN to avoid having to switch SCREEN modes to make modifications to the graphics image.

The _WIDTH function will get the width of a previously loaded image handle in total pixels. In the example code above the width value returned for Sky& is 640 which means that pixel coordinates 0 (zero) through 639 can be addressed in the image.

Likewise, the _HEIGHT function will get the height of a previously loaded image handle in total pixels. In the example code above the height value returned for Sky& is 480, which means that pixel coordinates 0 (zero) through 479 can be addressed in the image.


- _PUTIMAGE -

The _PUTIMAGE statement, in my opinion, is the most powerful graphics manipulation tool that QB64 offers to game programmers. _PUTIMAGE offers two major features; it can place graphic images on top of other graphic images and can grab an entire graphic image, or just a portion of it, to place on other graphic images. This is a very complex statement you have at your disposal, so let's take each feature one at a time to discover the possibilites that _PUTIMAGE places at your command.

The most basic feature of _PUTIMAGE is to place an image on top of another image or screen.


DIM Sky& '            handle to hold sky image
DIM Bee& '            handle to hold bee image
DIM TransparentBee& ' handle to hold bee image with transparent background

Sky& = _LOADIMAGE(".\GFX\sky.png", 32) '              load sky image into RAM
Bee& = _LOADIMAGE(".\GFX\bee0.png", 32) '             load bee image into RAM
TransparentBee& = _LOADIMAGE(".\GFX\tbee0.png", 32) ' load transparent bee image into RAM
SCREEN _NEWIMAGE(640, 480, 32) '                      create graphics screen
_PUTIMAGE (0, 0), Sky& '                              place sky image on screen
_PUTIMAGE (122, 171), Bee& '                          place bee image on screen
_PUTIMAGE (392, 171), TransparentBee& '               place transparent bee image on screen
SLEEP '                                               wait for key stroke

Two Bees
Figure 4 - One of these bees is not like the other

The simplest form of _PUTIMAGE is:

_PUTIMAGE (dx%, dy%), SourceHandle&

dx%           - the upper left x coordinate on the destination image or screen
dy%           - the upper left y coordinate on the destination image or screen
SourceHandle& - the source image handle to be placed on the current destination image or screen


SCREEN _NEWIMAGE(640, 480, 32) '                      create graphics screen
_PUTIMAGE (0, 0), Sky& '                              place sky image on screen


Since we know that the Sky& image is 640x480 in resolution we set up a new SCREEN with the same dimensions that supports 32 bit color. _PUTIMAGE is then used to place the Sky& image on top of the screen at coordinates (0, 0), or the top left of the screen. Since the Sky& image is the same dimension as the screen, _PUTIMAGE effectively covers the entire screen with the Sky& image. _PUTIMAGE uses the screen as the destination because it was the last image referenced, making it the default image being worked on. We'll delve more into destination and source images a bit later.

_PUTIMAGE (122, 171), Bee& '                          place bee image on screen
_PUTIMAGE (392, 171), TransparentBee& '               place transparent bee image on screen


These two lines of code then place the two bee images onto the screen at precalculated coordinates. Since I already knew the dimensions of the bee images, 135x135, I simply calculated where the two images would be centered on the screen. To get vertical centering the following equation was used:

(Screen Height - Image Height) \ 2 - 1

By substituting the numbers into the formula you get the following result:

(480 - 135) \ 2 - 1
   (345) \ 2 - 1
      172 - 1
        171

The -1 comes into play because the coordinate systems start with 0 (zero) on any image or screen. Since we are working with the overall height of the screen and image, 480 and 135, we need to take into account the coordinates inside these images is actually from 0 (zero) to their height and width -1.

To calculate where the images sit horizontally another quick math equation was used to determine this:

(Screen Width - (Image Width + Second Image Width)) \ 3

This calculation will give us the amount of width remaining after taking the two images into account and then dividing by 3 to give us the first bee's x coordinate:

(640 - (135 + 135)) \ 3 - 1
   (640 - 270) \ 3 - 1
      (370) \ 3 - 1
         123 - 1
           122

To keep equal spacing simply add the two widths to the bee's width and you get the second x coordinate:

122 + Image Width + Second Image Width
         122 + 135 + 135
            122 + 270
               392

But, what if the image dimensions are not known and you still want to maintain the same spacing? Well, simply do the calculations in the code, like so:


DIM Sky& '            handle to hold sky image
DIM Bee& '            handle to hold bee image
DIM TransparentBee& ' handle to hold bee image with transparent background
DIM BeeWidth% '       width of bee image
DIM BeeHeight% '      height of bee image
DIM TbeeWidth% '      width of transparent bee image
DIM TbeeHeight% '     height of transparent bee image
DIM BeeX% '           calculated x coordinate for bee image
DIM TbeeX% '          calculated x coordinate for transparent bee image
DIM BeeY% '           calculated y coordinate for bee image
DIM TbeeY% '          calculated y coordinate for transparent bee image

Sky& = _LOADIMAGE(".\GFX\sky.png", 32) '              load sky image into RAM
Bee& = _LOADIMAGE(".\GFX\bee0.png", 32) '             load bee image into RAM
TransparentBee& = _LOADIMAGE(".\GFX\tbee0.png", 32) ' load transparent bee image into RAM
SCREEN _NEWIMAGE(640, 480, 32) '                      create graphics screen
_PUTIMAGE (0, 0), Sky& '                              place sky image on screen
BeeWidth% = _WIDTH(Bee&) '                            get width of bee image
BeeHeight% = _HEIGHT(Bee&) '                          get height of bee image
TbeeWidth% = _WIDTH(TransparentBee&) '                get width of transparent bee image
TbeeHeight% = _HEIGHT(TransparentBee&) '              get height of transparent bee image
BeeX% = (640 - (BeeWidth% + TbeeWidth%)) \ 3 - 1 '    calculate bee x coordinate
BeeY% = (480 - BeeHeight%) \ 2 - 1 '                  calculate bee y coordinate
TbeeX% = BeeX% + (BeeWidth% * 2) '                    calculate transparent bee x coordinate
TbeeY% = (480 - TbeeHeight%) \ 2 - 1 '                calculate transparent bee y coordinate
_PUTIMAGE (BeeX%, BeeY%), Bee& '                      place bee image on screen
_PUTIMAGE (TbeeX%, TbeeY%), TransparentBee& '         place transparent bee image on screen
SLEEP '                                               wait for key stroke

However, this creates a lot of variables to keep track of so many programmers will simply embed calculations right into statements, like so:

DIM Sky& '  handle to hold sky image
DIM Bee& '  handle to hold bee image
DIM Tbee& ' handle to hold bee image with transparent background

Sky& = _LOADIMAGE(".\GFX\sky.png", 32) '    load sky image into RAM
Bee& = _LOADIMAGE(".\GFX\bee0.png", 32) '   load bee image into RAM
Tbee& = _LOADIMAGE(".\GFX\tbee0.png", 32) ' load transparent bee image into RAM
SCREEN _NEWIMAGE(640, 480, 32) '            create graphics screen
_PUTIMAGE (0, 0), Sky& '                    place sky image on screen
'*
'* calculate coordinates for images and place on screen
'*
_PUTIMAGE ((640 - (_WIDTH(Bee&) + _WIDTH(Tbee&))) \ 3 - 1, (480 - _HEIGHT(Bee&)) \ 2 - 1), Bee&

_PUTIMAGE ((640 - (_WIDTH(Bee&) + _WIDTH(Tbee&))) \ 3 - 1 + (_WIDTH(Bee&) * 2), (480 - _HEIGHT(Tbee&)) \ 2 - 1), Tbee&
SLEEP '                                     wait for key stroke

TransparentBee& was shortened to Tbee& to shorten the code up a bit. Yes, this makes for some very long and seemingly convoluted lines of code, but the results are exactly the same if you simply break the formulas down and look at them closely:

_PUTIMAGE ((640 - (_WIDTH(Bee&) + _WIDTH(Tbee&))) \ 3 - 1, (480 - _HEIGHT(Bee&)) \ 2 - 1), Bee&

Here is the x coordinate from the line above:

(640 - (_WIDTH(Bee&) + _WIDTH(Tbee&))) \ 3 - 1
        (640 - (135 + 135)) \ 3 - 1
           (640 - (270)) \ 3 - 1
               (370) \ 3 - 1
                  123 - 1
                    122


And here is the y coordinate from the line of code above:

(480 - _HEIGHT(Bee&)) \ 2 - 1
     (480 - 135) \ 2 - 1
        (345) \ 2 - 1
           172 - 1
             171


As you can see, the same results are acheived without the use of all the variables while breaking the equations down like this makes them completely readable.

Now, it may seem that I have gotten off track here a bit with the equations but what you need to understand is that _PUTIMAGE is going to be your main vehicle for placing and moving images around on the screen. Understanding how simple math calculations can be used to achieve proper image placement using a screen's or image's coordinate system is key to making a game with fluid movements. The math involved in the preceding equations contained nothing more than the four basic math operators; addition, subtraction, mutiplication and division (well, actually integer division given that we are working with whole number coordinates and need integers for final results). This sort of equation building will get easier with time, but there is nothing wrong with creating numerous variables as seen in one of the examples above to achieve the same results for now. When you encounter another programmer's code with what seems are mind-numbing equations simply break them down and study them. I've learned tons of excellent tips for equation building using other programmer's examples, and you will too.


- Transparency -

As mentioned before _LOADIMAGE supports transparent PNG image files. If a PNG file you are loading contains a transparent layer it will be maintained while using it with the various graphics manipulation tools offered by QB64. You can create transparent PNG images by using a paint program that supports the feature. A transparent layer in an image is simply a predefined color, or range of colors, that has been coded as being transparent. In other words, when that color, or color range, is encountered, instead of it being replicated on the screen the color behind the image is brought forward. This allows images to be placed over other images but maintain the original image as a background source. Let's take a look at our original _PUTIMAGE example again.

DIM Sky& '            handle to hold sky image
DIM Bee& '            handle to hold bee image
DIM TransparentBee& ' handle to hold bee image with transparent background

Sky& = _LOADIMAGE(".\GFX\sky.png", 32) '              load sky image into RAM
Bee& = _LOADIMAGE(".\GFX\bee0.png", 32) '             load bee image into RAM
TransparentBee& = _LOADIMAGE(".\GFX\tbee0.png", 32) ' load transparent bee image into RAM
SCREEN _NEWIMAGE(640, 480, 32) '                      create graphics screen
_PUTIMAGE (0, 0), Sky& '                              place sky image on screen
_PUTIMAGE (122, 171), Bee& '                          place bee image on screen
_PUTIMAGE (392, 171), TransparentBee& '               place transparent bee image on screen
SLEEP '                                               wait for key stroke

The Bee& image that was loaded contains no transparency information embedded within in it, so when placed on the screen the entire image area is shown covering anything that might have been behind it. However, the second image loaded, TransparentBee&, contains a transparency layer encoded within it. When this image is placed on the screen the transparent layer allows the image in the background to come forward. The transparency layer within an image is known as the alpha channel and QB64 has commands available for working with, and creating, alpha channels as well.

The _SETALPHA statement is used to create an alpha channel in images for transparency purposes. Let's use the code above once again but this time make our first bee image transparent:


DIM Sky& '            handle to hold sky image
DIM Bee& '            handle to hold bee image
DIM TransparentBee& ' handle to hold bee image with transparent background

Sky& = _LOADIMAGE(".\GFX\sky.png", 32) '              load sky image into RAM
Bee& = _LOADIMAGE(".\GFX\bee0.png", 32) '             load bee image into RAM
TransparentBee& = _LOADIMAGE(".\GFX\tbee0.png", 32) ' load transparent bee image into RAM
SCREEN _NEWIMAGE(640, 480, 32) '                      create graphics screen
_PUTIMAGE (0, 0), Sky& '                              place sky image on screen
_SETALPHA 0, _RGB32(255, 0, 255), Bee& '              make purple background transparent
_PUTIMAGE (122, 171), Bee& '                          place bee image on screen
_PUTIMAGE (392, 171), TransparentBee& '               place transparent bee image on screen
SLEEP '                                               wait for key stroke

The same
Figure 5 - A pair of transparent bees

Both of our bee images now contain transparency information. This line of code:

_SETALPHA 0, _RGB32(255, 0, 255), Bee& '              make purple background transparent

turned the purple background of the Bee& image into a transparent alpha channel. The _RGB32() function was used to identify the color to make transparent (more on _RGB32() in a bit). When the Bee& image was made with a paint program the purple background color was created by setting the RED value to 255 , the GREEN value to 0 (zero) and the BLUE value to 255. Mixing solid RED and BLUE achieves PURPLE, a very bright purple that most game creators will avoid given it's nasty appearance, which is why it makes a perfect color to use for creating transparency layers with. It's an easy color combination to remember.

The first value you must provide to _SETALPHA is the alpha level which ranges from 0 (zero, transparent) to 255 (opaque). Here we have set the alpha level to 0 (zero) making the purple color completely transparent. The second value provided to _SETALPHA is the color value (or color range) to convert to an alpha channel and the third value is the image handle you wish to apply the alpha channel to. By controlling the alpha channel and its properties through QB64 you can create some pretty cool effects, like this fade in/out effect the code below creates:


DIM Sky& '            handle to hold sky image
DIM Bee& '            handle to hold bee image
DIM TransparentBee& ' handle to hold bee image with transparent background
DIM Alpha% '          transparency level of image
DIM AlphaDir% '       direction of alpha level

Sky& = _LOADIMAGE(".\GFX\sky.png", 32) '              load sky image into RAM
Bee& = _LOADIMAGE(".\GFX\bee0.png", 32) '             load bee image into RAM
TransparentBee& = _LOADIMAGE(".\GFX\tbee0.png", 32) ' load transparent bee image into RAM
SCREEN _NEWIMAGE(640, 480, 32) '                      create graphics screen
AlphaDir% = 1 '                                       set initial direction of fade
DO
    CLS '                                             clear the screen
    _LIMIT 120 '                                      120 times per second
    _PUTIMAGE (0, 0), Sky& '                          place sky image on screen
    _PUTIMAGE (392, 171), TransparentBee& '           place transparent bee image on screen
    _SETALPHA Alpha%, _RGBA(0, 0, 0, 0) TO _RGBA(255, 255, 255, 255), Bee& ' apply to all
    _SETALPHA 0, _RGBA(255, 0, 255, Alpha%), Bee& '   keep purple completely transparent
    _PUTIMAGE (122, 171), Bee& '                      place bee image on screen
    Alpha% = Alpha% + AlphaDir% '                     adjust alpha value
    IF Alpha% = 255 OR Alpha% = 0 THEN '              reached alpha limit?
        AlphaDir% = -AlphaDir% '                      yes, reverse direction
    END IF
    _DISPLAY '                                        update screen with changes
LOOP UNTIL _KEYDOWN(27) '                             loop until ESC pressed

Here we are using _SETALPHA along with _RGBA(), a variant of _RGB32() that returns alpha channel information as well, to control the transparency level of all colors contained in Bee&. We'll explore more about transparency when we get to the color commands such as _RGB32 and _RGBA. The game techniques focused on in this course will mainly use a one color transparency setting to allow the background to be maintained while objects are moving on the screen. Here is an example of this in action:

DIM Sky& '      handle to hold sky image
DIM Bee0& '     handle to hold first bee image
DIM Bee1& '     handle to hold second bee image
DIM BeeDir% '   direction of bee
DIM BeeY% '     y location of bee
DIM WhichBee% ' indicates which bee image to show
DIM Buzz& '     the sound bees make

Sky& = _LOADIMAGE(".\GFX\sky.png", 32) '   load sky image into RAM
Bee0& = _LOADIMAGE(".\GFX\bee0.png", 32) ' load first bee image into RAM
Bee1& = _LOADIMAGE(".\GFX\bee1.png", 32) ' load second bee image into RAM
Buzz& = _SNDOPEN(".\SND\bee.ogg") '        what does the bee say?
_SETALPHA 0, _RGB32(255, 0, 255), Bee0& '  make purple transparent
_SETALPHA 0, _RGB32(255, 0, 255), Bee1& '  make purple transparent
BeeY% = 171 '                              set initial y coordinate of bee
BeeDir% = -1 '                             set initial direction of bee
WhichBee% = 1 '                            set initial bee image to show
SCREEN _NEWIMAGE(640, 480, 32) '           create graphics screen
_SNDLOOP Buzz& '                           loop the bee sound
DO
    CLS '                                  clear the screen
    _LIMIT 60 '                            60 times per second
    _PUTIMAGE (0, 0), Sky& '               place sky image on screen
    WhichBee% = 1 - WhichBee% '            toggle bee image indicator
    IF WhichBee% THEN '                    is value -1?
        _PUTIMAGE (252, BeeY%), Bee1& '    yes, place first bee image
    ELSE '                                 no, must be 0
        _PUTIMAGE (252, BeeY%), Bee0& '    place second bee image
    END IF
    BeeY% = BeeY% + BeeDir% '              increment bee y location
    IF BeeY% = 0 OR BeeY% = 344 THEN '     has y hit limits?
        BeeDir% = -BeeDir% '               yes, reverse direction of bee
    END IF
    _DISPLAY '                             update screen with changes
LOOP UNTIL _KEYDOWN(27) '                  loop until ESC pressed
SYSTEM '                                   return to Windows

Ah - a bee!
Figure 6 - Buzzing Around

A simple animation of a bee flying around on the screen with a little buzz for added flavor. The two bee images loaded are quickly switched back and forth to create the animation effect of the wings flapping. Each bee image is slightly different than the other which gives us the illusion of flapping wings. Since both images have been set to have transparent backgrounds the bee flies over the background image without destroying it in the process. This is pretty much the technique used when writing games that contain moving images on the screen.

Parallax scrolling is a method of making moving scenery seem to have a 3D effect and this can be achieved using transparent images as well. Once again we'll use _PUTIMAGE and a few transparent images to create a Mario-like world that moves by in 3D. Use the right and left arrow keys to move the scenery with a fake parallax effect that looks pretty good.

DIM Sky& '      handle to hold image of sky
DIM Ground& '   handle to hold image of ground
DIM Pillars& '  handle to hold image of pillars
DIM Mario& '    handle to hold mario music
DIM SkyX% '     sky image x location
DIM GroundX% '  ground image x location
DIM PillarsX% ' pillars image x location

Sky& = _LOADIMAGE(".\GFX\sky.png", 32) '          load sky image
Ground& = _LOADIMAGE(".\GFX\tground.png", 32) '   load ground image
Pillars& = _LOADIMAGE(".\GFX\tpillars.png", 32) ' load pillars image
Mario& = _SNDOPEN(".\SND\mario.ogg") '            load mario music
SCREEN _NEWIMAGE(640, 480, 32) '                  create graphics csreen
_SNDLOOP Mario& '                                 loop mario music
DO
    CLS '                                         clear screen
    _PUTIMAGE (SkyX%, 0), Sky& '                  place sky image
    _PUTIMAGE (SkyX% - 640, 0), Sky& '            place second sky image
    _PUTIMAGE (PillarsX%, 0), Pillars& '          place pillars image
    _PUTIMAGE (PillarsX% - 640, 0), Pillars& '    place second pillars image
    _PUTIMAGE (GroundX%, 0), Ground& '            place ground image
    _PUTIMAGE (GroundX% - 640, 0), Ground& '      place second ground image
    IF _KEYDOWN(19200) THEN '                     left arrow key down?
        SkyX% = SkyX% - 1 '                       yes, decrement sky x position
        IF SkyX% = -1 THEN SkyX% = 639 '          keep image within limits
        PillarsX% = PillarsX% - 2 '               decrement pillars x position
        IF PillarsX% = -2 THEN PillarsX% = 638 '  keep image within limits
        GroundX% = GroundX% - 4 '                 decrement ground x position
        IF GroundX% = -4 THEN GroundX% = 636 '    keep image within limits
    END IF
    IF _KEYDOWN(19712) THEN '                     right arrow key down?
        SkyX% = SkyX% + 1 '                       yes, increment sky x position
        IF SkyX% = 640 THEN SkyX% = 0 '           keep image within limits
        PillarsX% = PillarsX% + 2 '               increment pillars x position
        IF PillarsX% = 640 THEN PillarsX% = 2 '   keep image within limits
        GroundX% = GroundX% + 4 '                 increment ground x position
        IF GroundX% = 640 THEN GroundX% = 4 '     keep image within limits
    END IF
    _DISPLAY '                                    update screen with changes
LOOP UNTIL _KEYDOWN(27) '                         leave loop if ESC pressed
SYSTEM '                                          return to Windows

Whoa!
Figure 7 - Our parallax world

By placing transparent images over top of each other impressive, and quick, results like this parallaxing demo can be created. The key in creating something like this is to get the order of the images on the screen drawn in correct sequence. Since the sky will always be the furthest away there is not need for an alpha channel as long as it is always the first image placed on the screen. The second image of the pillars however does contain an alpha channel to allow the sky background image to come though. The third image of the ground also contains an alpha channel, allowing the sky and the pillars to come through as background images. If you were to reverse the order in which the pillar and ground images are drawn to the screen the effect would be the quite different.

Change this:


    _PUTIMAGE (SkyX%, 0), Sky& '                  place sky image
    _PUTIMAGE (SkyX% - 640, 0), Sky& '            place second sky image
    _PUTIMAGE (PillarsX%, 0), Pillars& '          place pillars image
    _PUTIMAGE (PillarsX% - 640, 0), Pillars& '    place second pillars image
    _PUTIMAGE (GroundX%, 0), Ground& '            place ground image
    _PUTIMAGE (GroundX% - 640, 0), Ground& '      place second ground image


to this:

    _PUTIMAGE (SkyX%, 0), Sky& '                  place sky image
    _PUTIMAGE (SkyX% - 640, 0), Sky& '            place second sky image

    _PUTIMAGE (GroundX%, 0), Ground& '            place ground image
    _PUTIMAGE (GroundX% - 640, 0), Ground& '      place second ground image

    _PUTIMAGE (PillarsX%, 0), Pillars& '          place pillars image
    _PUTIMAGE (PillarsX% - 640, 0), Pillars& '    place second pillars image


and now the bush will scroll behind the pillars.

- Using _PUTIMAGE to Alter Images-

The next feature of _PUTIMAGE we'll discuss is how to use it to change the size of an image, flip it horizontally, vertically or both. _PUTIMAGE supports using two coordinates to identify where the upper left and lower right corners of the image should be. By manipulating these coordinates _PUTIMAGE can alter the image. Let's re-examine our flying bee code but this time give it the appearance of flying away from and toward the screen.

DIM Sky& '      handle to hold sky image
DIM Bee0& '     handle to hold first bee image
DIM Bee1& '     handle to hold second bee image
DIM BeeDir% '   direction of bee
DIM BeeX% '     x center location of bee
DIM BeeY% '     y center location of bee
DIM BeeSize! '  size of bee (0 - 0% to 2 - 200%)
DIM SizeDir! '  direction size is going in (increasing or decreasing)
DIM WhichBee% ' indicates which bee image to show
DIM Buzz& '     what does the bee say?

Sky& = _LOADIMAGE(".\GFX\sky.png", 32) '     load sky image into RAM
Bee0& = _LOADIMAGE(".\GFX\bee0.png", 32) '   load first bee image into RAM
Bee1& = _LOADIMAGE(".\GFX\bee1.png", 32) '   load second bee image into RAM
Buzz& = _SNDOPEN(".\SND\bee.ogg") '          what does the bee say?
_SETALPHA 0, _RGB32(255, 0, 255), Bee0& '    make purple transparent
_SETALPHA 0, _RGB32(255, 0, 255), Bee1& '    make purple transparent
BeeX% = 319 '                                set initial x coordinate of bee
BeeY% = 171 '                                set initial y coordinate of bee
BeeDir% = -1 '                               set initial direction of bee
WhichBee% = 1 '                              set initial bee image to show
BeeSize! = 1 '                               set initial size of bee (100%)
SizeDir! = .01 '                             set initial size direction (getting larger)
SCREEN _NEWIMAGE(640, 480, 32) '             create graphics screen
_SNDLOOP Buzz& '                             loop the bee sound
DO
    CLS '                                    clear the screen
    _LIMIT 60 '                              60 times per second
    _PUTIMAGE (0, 0), Sky& '                 place sky image on screen
    WhichBee% = 1 - WhichBee% '              toggle bee image indicator
    BeeSize! = BeeSize! + SizeDir! '         increase bee size percentage
    IF BeeSize! > 2 OR BeeSize! < .25 THEN ' keep size of bee in limits
        SizeDir! = -SizeDir! '               reverse direction if limit hit
    END IF
    Adder% = 135 * BeeSize! \ 2 '            half size of bee image times percentage
    IF WhichBee% THEN '                      is value -1?
        _PUTIMAGE (BeeX% - Adder%, BeeY% - Adder%)-(BeeX% + Adder%, BeeY% + Adder%), Bee1&
    ELSE '                                   no, must be 0
        _PUTIMAGE (BeeX% - Adder%, BeeY% - Adder%)-(BeeX% + Adder%, BeeY% + Adder%), Bee0&
    END IF
    BeeY% = BeeY% + BeeDir% '                increment bee y location
    IF BeeY% = 0 OR BeeY% = 344 THEN '       has y hit limits?
        BeeDir% = -BeeDir% '                 yes, reverse direction of bee
    END IF
    _DISPLAY '                               update screen with changes
LOOP UNTIL _KEYDOWN(27) '                    loop until ESC pressed
SYSTEM '                                     return to Windows

In the original flying bee code we used _PUTIMAGE to place an image on the screen using only one coordinate, the upper left corner of the image. However, with this modified flying bee code we are specifying both corner coordinates for the image to be contained in through the use of _PUTIMAGE.

    IF WhichBee% THEN '                      is value -1?
        _PUTIMAGE (BeeX% - Adder%, BeeY% - Adder%)-(BeeX% + Adder%, BeeY% + Adder%), Bee1&
    ELSE '                                   no, must be 0
        _PUTIMAGE (BeeX% - Adder%, BeeY% - Adder%)-(BeeX% + Adder%, BeeY% + Adder%), Bee0&
    END IF


This second form of _PUTIMAGE is defined as:

_PUTIMAGE (dx1%, dy1%) - (dx2%, dy2%), SourceHandle&

dx1%          - the first corner x coordinate on the destination image or screen
dy1%          - the first corner y coordinate on the destination image or screen
dx2%          - the second corner x coordinate on the destination image or screen
dy2%          - the second corner y coordinate on the destination image or screen
SourceHandle& - the source image handle to be placed on the current destination image or screen

The coordinate pair above may seem like they are the upper left and lower right corners of the image you wish to place on the screen, however this coordinate pair actually defines how the image is to be placed onto the destination image or screen. The coordinate pair (dx1%, dy1%) will always define where the upper left corner of the image is to be placed on the destination image. Likewise, the coordinate pair (dx2%, dy2%) will always define where the lower right corner of the image is to be placed. But this does not mean that coordinate (dx1%, dy1%) must always be located somewhere to the upper left of coordinate (dx2%, dy2%). Once again, let's take the flying bee code and modify it to see how this new form of _PUTIMAGE can be used.

DIM Sky& '      handle to hold sky image
DIM Bee&(1) '   handle array to hold bee images
DIM BeeDir% '   direction of bee
DIM BeeX% '     x center location of bee
DIM BeeY% '     y center location of bee
DIM BeeSize! '  size of bee (0 - 0% to 2 - 200%)
DIM SizeDir! '  direction size is going in (increasing or decreasing)
DIM WhichBee% ' indicates which bee image to show (array index)
DIM Buzz& '     what does the bee say?
DIM Count% '    generic counter

Sky& = _LOADIMAGE(".\GFX\sky.png", 32) '     load sky image into RAM
FOR Count% = 0 TO 1 '                                                            cycle through indexes
    Bee&(Count%) = _LOADIMAGE(".\GFX\bee" + LTRIM$(STR$(Count%)) + ".png", 32) ' load bee image
    _SETALPHA 0, _RGB32(255, 0, 255), Bee&(Count%) '                             make purple transparent
NEXT Count%
Buzz& = _SNDOPEN(".\SND\bee.ogg") '          what does the bee say?
BeeX% = 319 '                                set initial x coordinate of bee
BeeY% = 171 '                                set initial y coordinate of bee
BeeDir% = -1 '                               set initial direction of bee
WhichBee% = 1 '                              set initial bee image to show
BeeSize! = 1 '                               set initial size of bee (100%)
SizeDir! = .01 '                             set initial size direction (getting larger)
SCREEN _NEWIMAGE(640, 480, 32) '             create graphics screen
_SNDLOOP Buzz& '                             loop the bee sound
DO
    CLS '                                    clear the screen
    _LIMIT 60 '                              60 times per second
    _PUTIMAGE (0, 0), Sky& '                 place sky image on screen
    WhichBee% = 1 - WhichBee% '              toggle bee image index
    BeeSize! = BeeSize! + SizeDir! '         increase bee size percentage
    IF BeeSize! > 2 OR BeeSize! < .25 THEN ' keep size of bee in limits
        SizeDir! = -SizeDir! '               reverse direction if limit hit
    END IF
    Adder% = 135 * BeeSize! \ 2 '            half size of bee image times percentage
    IF SGN(BeeDir%) = -1 THEN '              is bee heading up screen?
        _PUTIMAGE (BeeX% - Adder%, BeeY% - Adder%)-(BeeX% + Adder%, BeeY% + Adder%), Bee&(WhichBee%)
    ELSE '                                   no, bee is heading down screen
        _PUTIMAGE (BeeX% + Adder%, BeeY% + Adder%)-(BeeX% - Adder%, BeeY% - Adder%), Bee&(WhichBee%)
    END IF
    BeeY% = BeeY% + BeeDir% '                increment bee y location
    IF BeeY% = 0 OR BeeY% = 479 THEN '       has y hit limits?
        BeeDir% = -BeeDir% '                 yes, reverse direction of bee
    END IF
    _DISPLAY '                               update screen with changes
LOOP UNTIL _KEYDOWN(27) '                    loop until ESC pressed
SYSTEM '                                     return to Windows

A bee with direction
Figure 8 - Our bee now has a sense of direction

Before _PUTIMAGE's use is explained let's go over a few mods that were made to the code. The bee images were placed into an array for easier handling:

DIM Bee&(1) '   handle array to hold bee images

FOR Count% = 0 TO 1 '                                                            cycle through indexes
    Bee&(Count%) = _LOADIMAGE(".\GFX\bee" + LTRIM$(STR$(Count%)) + ".png", 32) ' load bee image
    _SETALPHA 0, _RGB32(255, 0, 255), Bee&(Count%) '                             make purple transparent
NEXT Count%


Inside the FOR...NEXT loop above we use the value contained in Count% to create a filename string to be used to load the bee image:

".\GFX\bee" + LTRIM$(STR$(Count%)) + ".png"

The first time through the FOR...NEXT loop Count% equals 0 (zero), creating the string:

".\GFX\bee0.png"

And the second time Count% equals 1 creating the string:

".\GFX\bee1.png"

This allows for both image files to be loaded into the array as the FOR...NEXT loop cycles through creating Bee&(0) and Bee&(1). The variable WhichBee% can now be used as an index number later in the program since it alternates between 0 (zero) and 1 allowing the program to animate the bee.

Next, we use the sign of the variable BeeDir% to get the direction the bee is currently traveling in; -1 indicates moving upward on the screen, 1 indicates moving downward. By identifying the direction the bee is traveling we can use the appropriate form of _PUTIMAGE to display the bee in the correct orientation:

    IF SGN(BeeDir%) = -1 THEN '              is bee heading up screen?
        _PUTIMAGE (BeeX% - Adder%, BeeY% - Adder%)-(BeeX% + Adder%, BeeY% + Adder%), Bee&(WhichBee%)
    ELSE '                                   no, bee is heading down screen
        _PUTIMAGE (BeeX% + Adder%, BeeY% + Adder%)-(BeeX% - Adder%, BeeY% - Adder%), Bee&(WhichBee%)
    END IF


If the bee is heading upward the coordinates given to _PUTIMAGE match the original image's orientation, that is the first coordinate pair is located left and up as compared to the second coordinate pair:

        _PUTIMAGE (BeeX% - Adder%, BeeY% - Adder%)-(BeeX% + Adder%, BeeY% + Adder%), Bee&(WhichBee%)

However, when the bee is traveling in a downward direction the coordinate pairs are reversed which in effect flips the bee image vertically:

        _PUTIMAGE (BeeX% + Adder%, BeeY% + Adder%)-(BeeX% - Adder%, BeeY% - Adder%), Bee&(WhichBee%)

Flipping the bee
Figure 9 - Flipping the bee

Therefore, according to Figure 9 above, it should be possible to flip an image horizontally, vertically or both at the same time by manipulating the starting and ending coordinate pair values. The next example program shows _PUTIMAGE doing just that. Use your mouse to move the bee around and manipulate the image in the center of the screen.

'--------------------------------
'- Variable Declaration Section -
'--------------------------------

DIM Sky& '      handle to hold sky image
DIM Bee& '      handle to hold bee image
DIM Arrows& '   handle to hold arrows image
DIM BeeX% '     x center location of bee
DIM BeeY% '     y center location of bee
DIM Angle% '    angle in degrees from bee to center of screen

'----------------------------
'- Main Program Begins Here -
'----------------------------

Sky& = _LOADIMAGE(".\GFX\sky.png", 32) '               load sky image into RAM
Arrows& = _LOADIMAGE(".\GFX\arrows.png", 32) '         load arrows image into RAM
Bee& = _LOADIMAGE(".\GFX\tbee0.png", 32) '             load bee image into RAM
BeeX% = 319 '                                          set initial x coordinate of bee
BeeY% = 70 '                                           set initial y coordinate of bee
SCREEN _NEWIMAGE(640, 480, 32) '                       create graphics screen
DO '                                                   begin main loop
    CLS '                                              clear the screen
    _LIMIT 60 '                                        60 times per second
    _PUTIMAGE (0, 0), Sky& '                           place sky image on screen
    _PUTIMAGE (BeeX% - 67, BeeY% - 67)-(BeeX% + 68, BeeY% + 68), Bee& ' place bee
    Angle% = INT(GETANGLE#(319, 239, BeeX%, BeeY%)) '  get angle from center screen to bee
    LOCATE 1, 1 '                                      position cursor
    PRINT "Angle ="; Angle% '                          show user current angle
    SELECT CASE Angle% '                               in which quadrant does angle fall?
        LOCATE 2, 1 '                                  position cursor
        CASE 0 TO 90 '                                 quadrant 1
            _PUTIMAGE (252, 172)-(387, 307), Arrows& ' normal image placement
            PRINT "Normal image placement"
        CASE 91 TO 180 '                               quadrant 2
            _PUTIMAGE (252, 307)-(387, 172), Arrows& ' flip image vertically
            PRINT "Arrows image flipped vertically"
        CASE 181 TO 270 '                              quadrant 3
            _PUTIMAGE (387, 307)-(252, 172), Arrows& ' flip image horizontally and vertically
            PRINT "Arrows image flipped vertically and horizontally"
        CASE 271 TO 359 '                              quadrant 4
            _PUTIMAGE (387, 172)-(252, 307), Arrows& ' flip image horizontally
            PRINT "Arrows image flipped horizontally"
    END SELECT
    WHILE _MOUSEINPUT: WEND '                          get latest mouse update
    BeeX% = _MOUSEX '                                  save current mouse x location
    BeeY% = _MOUSEY '                                  save current mouse y location
    _DISPLAY '                                         update screen with changes
LOOP UNTIL _KEYDOWN(27) '                              loop until ESC pressed
SYSTEM '                                               return to Windows

'-----------------------------------
'- Function and Subroutine section -
'-----------------------------------

FUNCTION GETANGLE# (x1#, y1#, x2#, y2#)

'*
'* Returns the angle in degrees from 0 to 359.9999.... between 2 given points.
'* Adapted from a function by Rob, aka Galleon, located in the QB64 Wiki
'*

IF y2# = y1# THEN '                                        both Y values same?
    IF x1# = x2# THEN '                                    yes, both X values same?
        EXIT FUNCTION '                                    yes, points are same, no angle
    END IF
    IF x2# > x1# THEN '                                    second X value greater?
        GETANGLE# = 90 '                                   yes, then must be 90 degrees
    ELSE '                                                 no, second X value is less
        GETANGLE# = 270 '                                  then must be 270 degrees
    END IF
    EXIT FUNCTION '                                        leave function
END IF
IF x2# = x1# THEN '                                        both X values same?
    IF y2# > y1# THEN '                                    second Y value greater?
        GETANGLE# = 180 '                                  yes, then must be 180 degrees
    END IF
    EXIT FUNCTION '                                        leave function
END IF
IF y2# < y1# THEN '                                        second Y value less?
    IF x2# > x1# THEN '                                    yes, second X value greater?
        GETANGLE# = ATN((x2# - x1#) / (y2# - y1#)) * -57.2957795131 ' yes, compute angle
    ELSE '                                                 no, second X value less
        GETANGLE# = ATN((x2# - x1#) / (y2# - y1#)) * -57.2957795131 + 360 ' compute angle
    END IF
ELSE '                                                     no, second Y value greater
    GETANGLE# = ATN((x2# - x1#) / (y2# - y1#)) * -57.2957795131 + 180 ' compute angle
END IF

END FUNCTION

Flipping arrows
Figure 10 - Flipping an image horizontally, vertically and both at the same time

- Colors -

Throughout the example programs in this task and a few others you have seen the use of statements and functions to control colors. Before we cover the various color functions and statements you need to have an understanding of how color is created using QB64.

Often you'll hear color depth, or the number of colors available, referred to in bits, such as 8 bit color or 32 bit color, but what does this mean? Let's start out with the simplest color depth available, 1 bit color, or good old black and white. Two colors are available using 1 bit color because each pixel of an image is capable of holding 21, or 2, different color combinations. In these early images and screens the color choices were black and white, where black meant the pixel is off or 0 (zero) and white meant the pixel is on or 1. Remember Chart 1 from Task 8 that listed all of the available screen modes available to early QuickBasic programmers? In that chart is a column labeled BPP, or bits per pixel, that represents how many colors are available for a given screen mode. By raising 2 to the power of this number you can calculate the number of colors available to the screen. Screen modes 2 and 11 are examples of 1 bit color screens, because they only offer 1 bit per pixel to store color information. These early images and screens were known as monochrome. Many early monochrome monitors used a green phosphor coating in them to create black and green screens which were easier on the eyes for extended periods of time.

The more bits available to a given pixel, or greater the BPP, the more colors the graphics screen or image has to choose from. Early video adapters such as CGA (16 color Color Graphics Array) and EGA (64 color Enhanced Graphics Array) used palletes to hold colors that were then available to the programmer to use on the screen. Thank goodness these legacy modes are a thing of the past because they were difficult to use and quite frankly by today's standards look horrible. QB64 still supports CGA and EGA color modes and their pallettes to choose from but games today require a minimum of 256 colors to keep gamers interested so we'll skip over CGA and EGA and start with VGA (Video Graphics Array) that offered 256 colors to the programmer.

Colors are achieved by mixing varying degrees of red, green and blue together to form new colors. You may remember doing this in grade school, where you learned that mixing blue and yellow for instance created green, or mixing red and blue together yields purple. All of today's graphics editing programs, such as Photoshop, using this mixing technigue to allow the artist to select a color from a pallette of millions.


Color Mixer
Figure 11 - Mixing colors

As Figure 11 shows, the Corel Color Picker, included with a program called PhotoImpact that I use for graphics, allows the artist to choose the color manually by supplying red, green and blue values from 0 to 255. In this case red and blue have been chosen with their highest values of 255 while green is completely turned off with a value of 0 (zero). This results in a bright purple color as shown in the color mix chart to the right. The following program shows how the red, green and blue color components can be mixed together in a 256 and 16 million color screen.

DIM Red& '       red color component
DIM Green& '     green color component
DIM Blue& '      blue color component
DIM Screen256& ' 256 color image
DIM Screen32& '  16M color image
DIM Smode% '     current screen mode
DIM Mode$(1) '   screen mode details

Screen256& = _NEWIMAGE(640, 480, 256) '                    create 256 color image
Screen32& = _NEWIMAGE(640, 480, 32) '                      create 16M color image
Mode$(0) = "256 color (8 bit)" '                           256 color mode details
Mode$(1) = "16M color (32 bit)" '                          16M color mode details
SCREEN Screen256& '                                        start in 256 color mode
DO
    _LIMIT 60 '                                            60 loops per second
    KeyPress$ = INKEY$ '                                   did user press a key?
    IF KeyPress$ = " " THEN ' '                            yes, was it the space bar?
        Smode% = 1 - Smode% '                              yes, toggle screen mode indicator
        SELECT CASE Smode% '                               which mode are we in?
            CASE 0 '                                       256 color mode
                SCREEN Screen256& '                        change to 256 color screen
            CASE 1 '                                       16M color mode
                SCREEN Screen32& '                         change to 16M color screen
        END SELECT
    END IF
    CLS '                                                  clear the screen
    CIRCLE (319, 239), 100, _RGB(Red&, Green&, Blue&) '    draw circle using color components
    PAINT (319, 239), _RGB(Red&, Green&, Blue&), _RGB(Red&, Green&, Blue&) ' paint the circle
    IF _KEYDOWN(113) THEN '                                is Q key down?
        Red& = Red& + 1 '                                  yes, increment red component
        IF Red& = 256 THEN Red& = 255 '                    keep it in limits
    END IF
    IF _KEYDOWN(97) THEN '                                 is A key down?
        Red& = Red& - 1 '                                  yes, decrement red component
        IF Red& = -1 THEN Red& = 0 '                       keep it in limits
    END IF
    IF _KEYDOWN(119) THEN '                                is W key down?
        Green& = Green& + 1 '                              yes, increment green component
        IF Green& = 256 THEN Green& = 255 '                keep it in limits
    END IF
    IF _KEYDOWN(115) THEN '                                is S key down?
        Green& = Green& - 1 '                              yes, decrement green component
        IF Green& = -1 THEN Green& = 0 '                   keep it in limits
    END IF
    IF _KEYDOWN(101) THEN '                                is E key down?
        Blue& = Blue& + 1 '                                yes, increment blue component
        IF Blue& = 256 THEN Blue& = 255 '                  keep it in limits
    END IF
    IF _KEYDOWN(100) THEN '                                is D key down?
        Blue& = Blue& - 1 '                                yes, decrement blue component
        IF Blue& = -1 THEN Blue& = 0 '                     keep it in limits
    END IF
    PRINT " MODE  : "; Mode$(Smode%), , "SPACEBAR to change modes" ' display info to user
    PRINT " RED   : Dec ="; Red&, " Hex = "; HEX$(Red&), "Q to increase  A to decrease "
    PRINT " GREEN : Dec ="; Green&, " Hex = "; HEX$(Green&), "W to increase  S to decrease "
    PRINT " BLUE  : Dec ="; Blue&, " Hex = "; HEX$(Blue&), "E to increase  D to decrease "
    PRINT " _RGB  : Dec ="; _RGB(Red&, Green&, Blue&), " Hex = "; RIGHT$(HEX$(_RGB(Red&, Green&, Blue&)), 6)
    PRINT " _RGB32: Dec ="; _RGB32(Red&, Green&, Blue&), " Hex = "; RIGHT$(HEX$(_RGB32(Red&, Green&, Blue&)), 6)
    _DISPLAY '                                             update screen with changes
LOOP UNTIL _KEYDOWN(27) '                                  leave when ESC key pressed
SYSTEM '                                                   return to Windows

Playing with colors
Figure 12 - Playing with colors

- _RGB( ) and _RGB32( ) -

The functions _RGB() and _RGB32() are used to mix the red, green and blue color components together and return a numeric value that represents the new mixed color. _RGB returns a value based on the color depth of the current image while _RGB32 will always return a 32 bit color value regardless of the current image's color depth. Let's start off by exploring _RGB32 first.

color32~& = _RGB32(red&, green&, blue&)

Red&, green& and blue& are the red, green and blue color component values used to create the 32 bit color value and they can range in value from 0 (zero) to 255. Color32~& is the unsigned long integer value returned resulting from the mixing of the three primary color components.

The _RGB32 function will always return an unsigned long integer value, that is it does not return negative numbers, only positive numbers ranging from 0 to 4,294,967,295. Task 5 introduced you to the various variable data types and one of those types was the long integer with a range of -2,147,483,648 to +2,147,483,647. This is actually a span of four billion numbers or 232. To allow for negative values when working with long integer numbers the number line has been slid downward half way, giving long integers the capability of showing two billion negative numbers as well as two billion positive numbers. However, 32 bit colors also have a span of four billion numbers but they must all be positive values. Therefore, an unsigned long integer variable type slides this number line back up two billion places to give us the 0 to 4,294,967,295 range of values. Unsigned long integer variables are created by using the appropriate type indentifier, which is a tilde ( ~ ) followed by an ampersand ( & ).


color32~& = _RGB32(255, 255, 0) ' yellow saved as unsigned long integer

Preceding most variable data types with a tilde ( ~ ) will result in that data type becoming unsigned and it's number base moved upward as was done with unsigned long integers.

In the previous program example you saw that no matter which bit depth the screen was in, 8 bit or 32bit, _RGB32 always returns a value of four billion and some change. That's because _RGB32 always returns a true 32 bit color value given the red, green and blue color components. If you would actually try to apply a 32 bit color value to a 256 color screen or image you would get an illegal function call error. Let's break down exactly how colors are created from three primary color components.

Some of you may have noticed by now that three 8 bit color components only adds up to 224 or 16,777,216 (16 million colors) because three 8 bit numbers next to each other equals 24 total bits. This is why we say 32 bit screens and images contain 16 million colors. But what about the other 8 bits that make up the 32 bit total? The value returned in the example program is always 4 billion or more? Remember the alpha channel we talked about earlier? Well, that's where the extra 8 bits come into play. This is easier to envision in a chart.


Breakdown of a 32 bit color
ChannelALPHAREDGREENBLUE
bits00000000 - 1111111100000000 - 1111111100000000 - 1111111100000000 - 11111111
dec0 - 2550 - 2550 - 2550 - 255
hex0 - FF0 - FF0 - FF0 - FF
Chart 1 - 32 bit color

_RGB32 only works with three of the four channels that make up a 32 bit color; the red, green and blue. By default colors are always opaque, or solid, therefore the alpha, or transparency, channel is always set to 255 or binary 11111111. Since the alpha bits are in the highest order place holders, and they are always on by default, you get a value that is always 4 billion or more. The color purple, for example, would break down like this:

The 32 bit color purple
Alpha ChannelRed ChannelGreen ChannelBlue Channel
11111111111111110000000011111111
23123022922822722622522422322222122021921821721621521421321221121029282726252423222120
21474836481073741824536870912268435456134217728671088643355443216777216838860841943042097152104857652428826214413107265536327681638481924096204810245122561286432168421
FFFF00FF
2552550255
Chart 2 - The color purple

Each channel in a 32 bit color contains 8 bits of information, therefore it can contain a decimal value from 0 to 255 (0 = 00000000 to 255 = 11111111). By placing the bits contained in each channel side by side we get a 32 bit binary number. Adding all the binary place values together that contain a value of 1 produces our rather large decimal number. To make it easier on the programmer we can simply enter each channel's value as an 8 bit number represented by a decimal value of 0 to 255. If you need a refresher on binary numbers or number bases visit Task 13 again.

Hexadecimal numbers are a kind of shorthand that programmers use to represent binary numbers. Hexadecimal is a base16 numbering system where each place holder is based on the number 16. Since there are not 16 individual numbers in the decimal numbering system the letters A through F are needed to represent the numbers 10 through 15. Hexadecimal is often used to represent color values in programming and HTML to design color into web pages. In Chart 2 above the hexadecimal value of 32 bit purple is FFFF00FF. Often times the alpha channel is ignored in this representation and only the three primary colors are used, where purple in this case would be FF00FF. In Figure 11 above you can see this at the bottom of the Corel Color Picker where it gives the hex value of #FF00FF for purple. A hexadecimal digit represents a binary nibble (4 bits) as the chart below shows.


DecimalBinary NibbleHexadecimal
000000
100011
200102
300113
401004
501015
601106
701117
810008
910019
101010A
111011B
121100C
131101D
141110E
151111F
Chart 3 - Decimal to Binary to Hexadecimal conversions

The information contained in Chart 3 above makes converting binary numbers to their hexadecimal equivalents an easy task. Simply start at the right-most bit of a binary number and move left in steps of four bits, then convert the nibble to it's hexadecimal equivalent. Repeat this process until all nibbles have been converted to a single hexadecimal value.

The 32 bit color burnt orange
2552041020
11111111110011000110011000000000
FFCC6600
Chart 4 - Burnt orange

In order to use the color burnt orange in your program you would supply _RGB32 with the values of 204 for RED, 102 for GREEN and 0 (zero) for BLUE.

BurntOrange~& = _RGB32(204, 102, 0) ' the color burnt orange

There are also many resources on the Internet that supply web designers with pre-defined colors and their hexadecimal values to use in their HTML source code. One example can be found here, and another one is here. You can use the hex values with _RGB and _RGB32 to obtain the color you seek. Let's use dark khaki with a value of BDB76B and
burnt orange with a value of CC6600 as an example:

DarkKhaki~& = _RGB32(&HBD, &HB7, &H6B) '   the color dark khaki
BurntOrange~& = _RGB32(&HCC, &H66, &H00) ' the color burnt orange from chart 4 above

Preceeding a number with an ampersand and then an H ( &H ) instructs QB64 that the number is in hexadecimal format. QB64 will automatically do the conversion to decimal for you but you must remember that hexadecimal numbers only contain the numbers 0 (zero) through 9 and A through F. Placing an &H in front of a non hexadecimal value will result in the IDE complaining the number is not a hexadecimal value. _RGB and _RGB32 will not accept the entire hexadecimal number as one value either. It must be broken up into bytes (2 nibbles) and each byte placed in the appropriate color component.

BurntOrange~& = _RGB32(&HCC6600) '         this will not work, incorrect number of arguments
BurntOrange~& = _RGB32(&HCC, &H66, &H00) ' this will work


The _RGB function handles colors differently depending on the bit depth of the current screen or image being worked with. When the screen or image being worked with has a 32 bit color depth, _RGB will act the same as _RGB32, that is it will return a true 32 bit color value. However, when the image or screen being worked with has an 8 bit color depth, or 256 colors, _RGB will return a value from 0 (zero) to 255 that as closely approximates the 32 bit color as possible. When using the example program shown in Figure 12 above you can see this happen in real time. When you press the Q key to increase the RED value you'll notice that when in 256 color mode you will not see the circle until the RED value reaches 33. Furthermore, if you hold down the Q key and allow the RED value to work its way to 255 you'll see that the circle changes between different brightnesses of RED only 5 times. This is because _RGB is approximating a color that is close on the 256 color pallete offered in 256 color mode. If you change to a 32 bit color mode by pressing the space bar you'll see that _RGB is now returning the same values as _RGB32. _RGB does not have to approximate a color any longer and can return a true 32 bit color value the same as _RGB32 does. The following program shows all the default colors available to a 256 color mode screen.

DIM c% ' current color
DIM x% ' x location of color box
DIM y% ' y location of color box

SCREEN _NEWIMAGE(640, 480, 256) '                  256 color screen
CLS '                                              clear screen
FOR y% = 0 TO 479 STEP 30 '                        16 boxes vertical
    FOR x% = 0 TO 639 STEP 40 '                    16 boxes horizontal
        LINE (x%, y%)-(x% + 39, y% + 29), c%, BF ' draw color box
        LOCATE 2, 1 '                              locate cursor
        PRINT c%; '                                print color value
        c% = c% + 1 '                              increment color value
        _DELAY .0125 '                             very short pause
    NEXT x%
NEXT y%
SLEEP '                                            wait for keystroke
SYSTEM '                                           return to Window

Pretty
Figure 13 - 256 colors ... pretty

- Destination and Source Images -

Before we get into how to use _PUTIMAGE to grab images, or selected areas of images, we need to discuss the concept of destination and source images first. To put it simply, the destination image, or screen, is where a graphics statement will perform its work. The source image, or screen, is where the information for a statement, such as a color, image or partial image, is gathered from. An easy way to remember this is if a statement or function needs to gather information from an image or screen it will use the source image or screen to do so. If a statement or function modifies a screen or image in any way it will use the destination image or screen to do so. Some of the QB64 graphics statements allow for the the source, destination or both to be identified within the statement itself, others will need to be told by setting the source and destination manually.

- _DEST and _SOURCE -

The _DEST and _SOURCE statements are used to set the destination and source image or screen. The syntax for both statements is:

_DEST handle&

_SOURCE handle&


Handle& is a valid screen or image handle that will act as the current write or read image or screen. Supplying a value of 0 (zero) for handle& with _DEST or _SOURCE will always refer to the current SCREEN.

DIM Bee& ' handle to hold bee image
DIM Sky& ' handle to hold sky image

Bee& = _LOADIMAGE(".\GFX\tbee0.png", 32) '   load bee image into RAM
Sky& = _LOADIMAGE(".\GFX\sky.png", 32) '     load sky image into RAM
SCREEN _NEWIMAGE(640, 480, 32) '             create 32 bit screen
CLS '                                        clear screen
_PUTIMAGE (0, 0), Sky& '                     default DEST is screen
_DEST Bee& '                                 DEST is now bee image
CIRCLE (67, 67), 50, _RGB32(255, 255, 255) ' circle will get drawn on bee image
_DEST 0 '                                    DEST is now current SCREEN
_PUTIMAGE (252, 172), Bee& '                 bee image placed on screen

In the program example above the first _PUTIMAGE statement places the Sky& image on the current screen. The destination and source always default to 0 (zero) which indicates the SCREEN is always the default source and destination for images. We then change the destination to the Bee& image handle indicating that any graphics statements used from this point on will use the Bee& image as the destination location for their commands to be carried out. In this case we are creating a white circle at x, y location 67, 67 with a radius of 50. However, the CIRCLE statement will perform it's task on the Bee& image since this is the new destination. We then set the destination back to the default screen by supplying a value of 0 (zero) as the destination. _PUTIMAGE then centers the Bee& image onto the screen showing that in fact the Bee& image contains the circle we created earlier.

This destination swapping becomes useful to a game programmer because it can speed the processing of graphics images. For instance, most games have a score counter that keeps track of the player's score. This counter can be maintained with a small image in RAM, for instance ScoreImg&, and whenever the score changes the ScoreImg& image is updated to reflect the change. Since this image is most likely very small, the update is much quicker than trying to update the score directly to the SCREEN. With each frame of the game passing by the ScoreImg& image is placed on the screen at the appropriate location using a _PUTIMAGE statement. This in general creates much faster screen and image updates which translates to faster frames per second. We'll get more into these game design concepts in the upcoming Task 17.


_DEST and _SOURCE can also be used as functions to return the current destination and source handle values.

handle& = _DEST

handle& = _SOURCE

Handle& in both instances above will contain the value of the current destination and source images or screens.


- An Example -

We have covered a lot of concepts so far in this task. Therefore, it's time to put all of these concepts together into a program that highlights each. Copy and paste the following program into your IDE and then execute it. Use your mouse to make the magic happen. :)

'*
'* Hocus Pocus V2.2 by Terry Ritchie 01/24/14
'*
'* Use the mouse to create magic. Press ESC to leave this magical place.
'*

'--------------------------------
'- Variable Declaration Section -
'--------------------------------

CONST FALSE = 0, TRUE = NOT FALSE

CONST SWIDTH = 640 '       screen width
CONST SHEIGHT = 480 '      screen height
CONST BLOOMAMOUNT = 5 '    number of blooms per mouse movement (don't go too high!)
CONST MAXSIZE = 64 '       maximum size of blooms (don't go too high!)
CONST MAXLIFE = 32 '       maximum life time on screen
CONST MAXXSPEED = 6 '      maximum horizontal speed at bloom creation
CONST MAXYSPEED = 10 '     maximum vertical speed at bloom creation
CONST BOUNCE = FALSE '     set to TRUE to have blooms bounce off bottom of screen

TYPE CADABRA '             image properties
    lifespan AS INTEGER '  life span of bloom on screen
    x AS SINGLE '          x location of bloom
    y AS SINGLE '          y location of bloom
    size AS INTEGER '      size of bloom
    xdir AS SINGLE '       horizontal direction of bloom
    ydir AS SINGLE '       vertical direction of bloom
    xspeed AS SINGLE '     horizontal speed of bloom
    yspeed AS SINGLE '     vertical speed of bloom
    image AS LONG '        bloom image handle
    freed AS INTEGER '     boolean indicating if image handle has been freed
END TYPE

REDIM Abra(1) AS CADABRA ' dynamic array to hold properties
DIM x% '                   current x position of mouse
DIM y% '                   current y position of mouse
DIM Oldx% '                previous x position of mouse
DIM Oldy% '                previous y position of mouse
DIM Blooms% '              bloom counter
DIM sa& '                  Sorcerer's Apprentice sound file

'----------------------------
'- Main Program Begins Here -
'----------------------------

SCREEN _NEWIMAGE(SWIDTH, SHEIGHT, 32) '      create 32 bit graphics screen
_SCREENMOVE _MIDDLE '                        move window to center of desktop
sa& = _SNDOPEN(".\SND\apprentice.ogg") '     load sound file into RAM
_SNDLOOP sa& '                               play music in continuous loop
_MOUSEHIDE '                                 hide the mouse pointer
_MOUSEMOVE SWIDTH \ 2, SHEIGHT \ 2 '         move mouse pointer to middle of screen
WHILE _MOUSEINPUT: WEND '                    get latest mouse information
x% = _MOUSEX '                               get current mouse x position
y% = _MOUSEY '                               get current mouse y position
Oldx% = x% '                                 remember mouse x position
Oldy% = y% '                                 remember mouse y position
Abra(1).freed = TRUE '                       first index is free to use
RANDOMIZE TIMER '                            seed random number generator
DO '                                         begin main loop
    _LIMIT 30 '                              30 frames per second
    WHILE _MOUSEINPUT: WEND '                get latest mouse information
    x% = _MOUSEX '                           get current mouse x position
    y% = _MOUSEY '                           get current mouse y position
    IF (Oldx% <> x%) OR (Oldy% <> y%) THEN ' has mouse moved since last loop?
        FOR Blooms% = 1 TO BLOOMAMOUNT '     yes, create set number of blooms
            HOCUS x%, y% '                   create bloom at current mouse location
        NEXT Blooms%
        Oldx% = x% '                         remember mouse x position
        Oldy% = y% '                         remember mouse y position
    END IF
    CLS '                                    clear screen
    POCUS '                                  draw active blooms
    _DISPLAY '                               update screen with changes
LOOP UNTIL _KEYDOWN(27) '                    leave when ESC pressed
SYSTEM '                                     return to Windows

'-----------------------------------
'- Function and Subroutine section -
'-----------------------------------

'----------------------------------------------------------------------------------------------------------------------

SUB HOCUS (hx%, hy%)

'*
'* Maintains the bloom array by creating bloom properties for a new bloom.
'* If no array indexes are free a new one is added to the end of the array to
'* hold the new bloom. If an unused index is available the new bloom will occupy
'* that free index position. If no blooms are currently active the array is
'* erased and reset to an index of 1 to be built again.
'*
'* hx% - x location of new bloom
'* hy% - y location of new bloom
'*

SHARED Abra() AS CADABRA ' need access to bloom array

DIM CleanUp% '             if true array will be reset
DIM Count% '               generic counter
DIM Index% '               array index to create new bloom in
DIM OriginalDest& '        destination screen/image of calling routine
DIM Red% '                 red color component of bloom
DIM Green% '               green color component of bloom
DIM Blue% '                blue color component of bloom
DIM RedStep% '             red fade amount
DIM GreenStep% '           green fade amount
DIM BlueStep% '            blue fade amount
DIM Alpha% '               alpha channel fade amount

CleanUp% = TRUE '                                           assume array will need reset
Index% = 0 '                                                reset available index marker
Count% = 1 '                                                start array index counter at 1
DO WHILE Count% <= UBOUND(Abra) '                           cycle through entire array
    IF Abra(Count%).lifespan = 0 THEN '                     has this image run its course?
        IF NOT Abra(Count%).freed THEN '                    yes, has the image been freed from RAM?
            _FREEIMAGE Abra(Count%).image '                 no, remove the image from RAM
            Abra(Count%).freed = TRUE '                     remember that it has been removed
        END IF
        IF Index% = 0 THEN '                                has an available array index been chosen?
            Index% = Count% '                               no, mark this array index as available
        END IF
    ELSE '                                                  no, this image is still active
        CleanUp% = FALSE '                                  do not clear the array
    END IF
    Count% = Count% + 1 '                                   increment array index counter
LOOP
IF CleanUp% THEN '                                          have all images run their course?
    REDIM Abra(1) AS CADABRA '                              yes, reset the array
    Abra(1).freed = TRUE '                                  there is no image here yet
    Index% = 1 '                                            mark first index as available
ELSE '                                                      no, there are still active images
    IF Index% = 0 THEN '                                    were all the images in the array active?
        REDIM _PRESERVE Abra(UBOUND(abra) + 1) AS CADABRA ' yes, increase the array size by 1
        Index% = UBOUND(abra) '                             mark top index as available
    END IF
END IF
Abra(Index%).lifespan = INT(RND(1) * MAXLIFE) + 16 '        random length of time to live (frames)
Abra(Index%).x = hx% '                                      bloom x location
Abra(Index%).y = hy% '                                      bloom y location
Abra(Index%).size = INT(RND(1) * (MAXSIZE * .75) + (MAXSIZE * .25)) ' random size of bloom
Abra(Index%).xdir = (RND(1) - RND(1)) * 3 '                 random horizontal direction of bloom
Abra(Index%).ydir = -1 '                                    vertical direction of bloom (up)
Abra(Index%).xspeed = INT(RND(1) * MAXXSPEED) '             random horizontal speed of bloom
Abra(Index%).yspeed = INT(RND(1) * MAXYSPEED) '             random vertical speed of bloom
Abra(Index%).image = _NEWIMAGE(Abra(Index%).size * 2, Abra(Index%).size * 2, 32) ' create image holder
Red% = INT(RND(1) * 255) + 1 '                              random red component value
Green% = INT(RND(1) * 255) + 1 '                            random green compoenent value
Blue% = INT(RND(1) * 255) + 1 '                             random blue component value
RedStep% = (255 - Red%) \ Abra(Index%).size '               random fade of red component
GreenStep% = (255 - Green%) \ Abra(Index%).size '           random fade of green component
BlueStep% = (255 - Blue%) \ Abra(Index%).size '             random fade of blue component
AlphaStep! = 255 \ Abra(Index%).size '                      compute fade of alpha channel
Alpha% = 0 '                                                start alpha channel completely transparent
OriginalDest& = _DEST '                                     save calling routine's destination screen/image
_DEST Abra(Index%).image '                                  set destination to bloom image
Count% = Abra(Index%).size '                                start from outside of bloom working in
DO WHILE Count% > 0 '                                       start bloom drawing loop
    '*
    '* Draw circle with current red, green, blue components
    '*
    CIRCLE (_WIDTH(Abra(Index%).image) / 2, _HEIGHT(Abra(Index%).image) / 2), Count%, _RGB32(Red%, Green%, Blue%)
    '*
    '* Paint circle with current red, green, blue components
    '*
    PAINT (_WIDTH(Abra(Index%).image) / 2, _HEIGHT(Abra(Index%).image) / 2), _RGB32(Red%, Green%, Blue%), _RGB32(Red%, Green%, Blue%)
    _SETALPHA Alpha%, _RGB32(Red%, Green%, Blue%) '         set transparency level of current color
    Red% = Red% + RedStep% '                                increase red component
    Green% = Green% + GreenStep% '                          increase green component
    Blue% = Blue% + BlueStep% '                             increase blue component
    Alpha% = Alpha% + AlphaStep! '                          increase opacity level of alpha channel
    Count% = Count% - 1 '                                   decrease size of circle
LOOP '                                                      leave loop when smallest circle drawn
_DEST OriginalDest& '                                       return original destination to calling routine

END SUB

'----------------------------------------------------------------------------------------------------------------------

SUB POCUS ()

'*
'* places active blooms onto the screen or current image and updates their
'* position, size and speed
'*

SHARED Abra() AS CADABRA ' need access to bloom array

DIM c% '                   array index counter
DIM o% '                   bloom image size x,y offset

c% = UBOUND(Abra) '                                                 start at top of array
DO WHILE c% > 0 '                                                   loop until beginning of array
    IF Abra(c%).lifespan > 0 THEN '                                 is this bloom active?
        o% = INT(Abra(c%).size) '                                   yes, get current size of bloom image
        _PUTIMAGE (Abra(c%).x - o%, Abra(c%).y - o%)-(Abra(c%).x + o%, Abra(c%).y + o%), Abra(c%).image
        Abra(c%).lifespan = Abra(c%).lifespan - 1 '                 decrement lifespan of bloom
        Abra(c%).size = Abra(c%).size * .95 '                       decrease size of bloom slightly
        Abra(c%).x = Abra(c%).x + Abra(c%).xdir * Abra(c%).xspeed ' update x position of bloom
        Abra(c%).y = Abra(c%).y + Abra(c%).ydir * Abra(c%).yspeed ' update y position of bloom
        IF Abra(c%).y > SHEIGHT - 1 THEN '                          has bloom left bottom of screen?
            IF BOUNCE THEN '                                        should bloom bounce?
                Abra(c%).yspeed = -Abra(c%).yspeed '                yes, reverse y velocity
            ELSE '                                                  no
                Abra(c%).lifespan = 0 '                             kill it, no longer needed
            END IF
        END IF
        Abra(c%).xspeed = Abra(c%).xspeed * .9 '                    decrease x velocity slightly
        Abra(c%).yspeed = Abra(c%).yspeed - .5 '                    decrease y velocity (simulating gravity)
    END IF
    c% = c% - 1 '                                                   decrement to next index in array
LOOP

END SUB

'----------------------------------------------------------------------------------------------------------------------

Alakazaam
Figure 14 - Hocus Pocus!

Pretty impressive! Just imagine the math involved in this program! Right? Nope, this was achieved using the concepts learned in this task and others. Each bloom is actually an individual square image containing a transparency layer that over time is made smaller and smaller using _PUTIMAGE's two coordinate placement system to give the illusion of fading out. The blooms themselves are created using the CIRCLE statement contained in a loop to draw ever smaller circles of slightly varying color that eventually leads to bright white, giving them the appearance of glowing multicolored lights. There's no code Kung Fu going on here, just concepts from this task used to create an interesting effect. In games these are known as particle effects and can add real flare to game play. Let's go through the program to see how these concepts have been applied to gain an understanding of their use.

Since we need to track many images at the same time on the screen we used the concepts of arrays described in Task 11 to set up a TYPE structure to place within a dynamic array. This TYPE holds all of the various values we will need to keep track of each bloom image.


TYPE CADABRA '             image properties
    lifespan AS INTEGER '  life span of bloom on screen
    x AS SINGLE '          x location of bloom
    y AS SINGLE '          y location of bloom
    size AS INTEGER '      size of bloom
    xdir AS SINGLE '       horizontal direction of bloom
    ydir AS SINGLE '       vertical direction of bloom
    xspeed AS SINGLE '     horizontal speed of bloom
    yspeed AS SINGLE '     vertical speed of bloom
    image AS LONG '        bloom image handle
    freed AS INTEGER '     boolean indicating if image handle has been freed
END TYPE

REDIM Abra(1) AS CADABRA ' dynamic array to hold properties

The dynamic array Abra() can now be used to hold any number of the CADABRA TYPEs. As described in Task 11 we have basically set up a spreadsheet for our values to be stored into, retrieved and changed later on.

Inside of the main program section we create the graphics screen, move the mouse to the center of the screen, hide it and remember its position. All of this is done before the main program loop begins.


SCREEN _NEWIMAGE(SWIDTH, SHEIGHT, 32) '      create 32 bit graphics screen
_SCREENMOVE _MIDDLE '                        move window to center of desktop
sa& = _SNDOPEN(".\SND\apprentice.ogg") '     load sound file into RAM
_SNDLOOP sa& '                               play music in continuous loop
_MOUSEHIDE '                                 hide the mouse pointer
_MOUSEMOVE SWIDTH \ 2, SHEIGHT \ 2 '         move mouse pointer to middle of screen
WHILE _MOUSEINPUT: WEND '                    get latest mouse information
x% = _MOUSEX '                               get current mouse x position
y% = _MOUSEY '                               get current mouse y position
Oldx% = x% '                                 remember mouse x position
Oldy% = y% '                                 remember mouse y position
Abra(1).freed = TRUE '                       first index is free to use
RANDOMIZE TIMER '                            seed random number generator


In the main program loop we're basically watching the mouse for changes in movement. If the current mouse position has changed since the previous loop the HOCUS subroutine is instructed to create a set of blooms at the current mouse position. The POCUS subroutine is in charge of maintaining the blooms for their intended life span.

DO '                                         begin main loop
    _LIMIT 30 '                              30 frames per second
    WHILE _MOUSEINPUT: WEND '                get latest mouse information
    x% = _MOUSEX '                           get current mouse x position
    y% = _MOUSEY '                           get current mouse y position
    IF (Oldx% <> x%) OR (Oldy% <> y%) THEN ' has mouse moved since last loop?
        FOR Blooms% = 1 TO BLOOMAMOUNT '     yes, create set number of blooms
            HOCUS x%, y% '                   create bloom at current mouse location
        NEXT Blooms%
        Oldx% = x% '                         remember mouse x position
        Oldy% = y% '                         remember mouse y position
    END IF
    CLS '                                    clear screen
    POCUS '                                  draw active blooms
    _DISPLAY '                               update screen with changes
LOOP UNTIL _KEYDOWN(27) '                    leave when ESC pressed

Inside the HOCUS subroutine the dynamic array is scanned for active and inactive blooms. If an active bloom is found the variable CleanUp% is set to FALSE, which indicates the array should not be completely erased and reset back to an index value of 1. If an inactive bloom is found that index number is marked as being free to use for the new bloom that is about to created and the old bloom image is freed from RAM using _FREEIMAGE. This allows the unused array indexes to be recycled instead of increasing the dynamic array's size to hold the new bloom information. If, however, all indexes were seen to be free, the array is wiped clean and reset to an index value of 1 using REDIM and this first index is used to create the new bloom. This ensures that during periods of no mouse activity, and all blooms have run their course, that the dynamic array is cleared releasing the used space from RAM and keeping the array to minimum size for speed.

CleanUp% = TRUE '                                           assume array will need reset
Index% = 0 '                                                reset available index marker
Count% = 1 '                                                start array index counter at 1
DO WHILE Count% <= UBOUND(Abra) '                           cycle through entire array
    IF Abra(Count%).lifespan = 0 THEN '                     has this image run its course?
        IF NOT Abra(Count%).freed THEN '                    yes, has the image been freed from RAM?
            _FREEIMAGE Abra(Count%).image '                 no, remove the image from RAM
            Abra(Count%).freed = TRUE '                     remember that it has been removed
        END IF
        IF Index% = 0 THEN '                                has an available array index been chosen?
            Index% = Count% '                               no, mark this array index as available
        END IF
    ELSE '                                                  no, this image is still active
        CleanUp% = FALSE '                                  do not clear the array
    END IF
    Count% = Count% + 1 '                                   increment array index counter
LOOP
IF CleanUp% THEN '                                          have all images run their course?
    REDIM Abra(1) AS CADABRA '                              yes, reset the array
    Abra(1).freed = TRUE '                                  there is no image here yet
    Index% = 1 '                                            mark first index as available
ELSE '                                                      no, there are still active images
    IF Index% = 0 THEN '                                    were all the images in the array active?
        REDIM _PRESERVE Abra(UBOUND(abra) + 1) AS CADABRA ' yes, increase the array size by 1
        Index% = UBOUND(abra) '                             mark top index as available
    END IF
END IF


Now that an index value has been identified to create the new bloom in, the variables needed to create the bloom are given values. The bloom's location was passed into the HOCUS subroutine as hx% and hy% and these are saved in the array as the location to create the new bloom. A random life span for the bloom is created, as well as a random size, direction and horizontal speed. Finally, an image holder is created in RAM and its handle value saved for later use when the bloom is to be drawn in the image. Remember how it was mentioned that when a new image is created the color value of (0, 0, 0) is by default transparent and using CLS removes this? Well, here we use that to our benefit and leave it transparent, giving us a black transparent canvas to draw on.

Abra(Index%).lifespan = INT(RND(1) * MAXLIFE) + 16 '        random length of time to live (frames)
Abra(Index%).x = hx% '                                      bloom x location
Abra(Index%).y = hy% '                                      bloom y location
Abra(Index%).size = INT(RND(1) * (MAXSIZE * .75)) + MAXSIZE * .25 ' random size of bloom
Abra(Index%).xdir = (RND(1) - RND(1)) * 3 '                 random horizontal direction of bloom
Abra(Index%).ydir = -1 '                                    vertical direction of bloom (up)
Abra(Index%).xspeed = INT(RND(1) * MAXXSPEED) '             random horizontal speed of bloom
Abra(Index%).yspeed = INT(RND(1) * MAXYSPEED) '             random vertical speed of bloom
Abra(Index%).image = _NEWIMAGE(Abra(Index%).size * 2, Abra(Index%).size * 2, 32) ' create image holder


Our bloom now has it's life, location, speed and image characteristics so it's time to actually draw the bloom onto the image. We start by assigning the three primary red, green and blue color components a random value between 1 and 255 to ensure that we get almost the entire range of 16 million colors available. Next a value is computed for each of the primary colors that defines the step value that is needed to increase the brightness to a full value of 255 given the radius of the largest circle that will be drawn. The radius is the random size of the bloom that was previoulsy computed. This is why when the image file was created in RAM the width and height values were multiplied by 2 to account for the overall diameter of the initial circle that will be drawn. Also, a step value is computed that will take the alpha channel of the circles drawn from transparent to opaque as the circles get smaller and closer to the center, making them appear to glow brighter as you get closer to the center.

Red% = INT(RND(1) * 255) + 1 '                              random red component value
Green% = INT(RND(1) * 255) + 1 '                            random green compoenent value
Blue% = INT(RND(1) * 255) + 1 '                             random blue component value
RedStep% = (255 - Red%) \ Abra(Index%).size '               random fade of red component
GreenStep% = (255 - Green%) \ Abra(Index%).size '           random fade of green component
BlueStep% = (255 - Blue%) \ Abra(Index%).size '             random fade of blue component
AlphaStep! = 255 \ Abra(Index%).size '                      compute fade of alpha channel


Before we can draw the bloom onto the image the current destination must be saved and the new destination pointed at the image file. Since the subroutine has been called from some other location in the program we need to ensure to retain the destination integrity of the calling routine by saving its value so we point back to the original destination later on.

OriginalDest& = _DEST '                                     save calling routine's destination screen/image
_DEST Abra(Index%).image '                                  set destination to bloom image


To draw the bloom we start with the size of the bloom which will be our starting circle's radius. The DO WHILE ... LOOP counts down from the radius size to 1 which allows us to draw ever increasily smaller circles as we draw the bloom. A circle is now drawn in the center of the image with the current red, green and blue components mixed using _RGB32 at the current size. Then the PAINT statement is used to fill the circle in with the same color. This color is then given transparency properties by the _SETALPHA statement. The red, green and blue color components are now increased by the step value computed earlier to make them a bit brighter. The alpha channel value is also increased by its step value to make it slightly more opaque. The value of the circle's radius is decreased by 1 and the whole process repeats again. The next time around however, the circle is a bit smaller, the colors a bit brighter and the transparency level a bit more opaque. This continues on until the the last circle is drawn with a radius of 1, the color components are all maxed out with a value of 255 giving us a bright white center and the transparency is completely opaque, or solid. We just created a glowing bloom!

Count% = Abra(Index%).size '                                start from outside of bloom working in
DO WHILE Count% > 0 '                                       start bloom drawing loop
    '*
    '* Draw circle with current red, green, blue components
    '*
    CIRCLE (_WIDTH(Abra(Index%).image) / 2, _HEIGHT(Abra(Index%).image) / 2), Count%, _RGB32(Red%, Green%, Blue%)
    '*
    '* Paint circle with current red, green, blue components
    '*
    PAINT (_WIDTH(Abra(Index%).image) / 2, _HEIGHT(Abra(Index%).image) / 2), _RGB32(Red%, Green%, Blue%), _RGB32(Red%, Green%, Blue%)
    _SETALPHA Alpha%, _RGB32(Red%, Green%, Blue%) '         set transparency level of current color
    Red% = Red% + RedStep% '                                increase red component
    Green% = Green% + GreenStep% '                          increase green component
    Blue% = Blue% + BlueStep% '                             increase blue component
    Alpha% = Alpha% + AlphaStep! '                          increase opacity level of alpha channel
    Count% = Count% - 1 '                                   decrease size of circle
LOOP '                                                      leave loop when smallest circle drawn
_DEST OriginalDest& '                                       return original destination to calling routine

A bloom is born
Figure 15 - A bloom is born

The last line of code above points the destination back to the original calling routine's destination. If we didn't do this the last bloom image would still be the destination and all subsequent graphics statements would try to use this image creating a real mess.

While the HOCUS subroutine creates the blooms in the dynamic array it's up to the POCUS subroutine to draw them to the screen, making them a bit smaller each time they are drawn until their life span runs out. This gives the illusion that the blooms are burning out and fading away while they travel. The first thing we need to do is get the size of the dynamic array, or its last index value, and use this as a count value while we cycle down through the dynamic array.

c% = UBOUND(Abra) '                                                 start at top of array

Next we set up a loop and cycle from this value to the first index value of 1 looking for any blooms that are still "alive" with a life span value greater than 0 (zero).

DO WHILE c% > 0 '                                                   loop until beginning of array
    IF Abra(c%).lifespan > 0 THEN '                                 is this bloom active?


If a bloom is found to be active it is then drawn to the screen, using the size of the bloom as an offset value to compute the upper left hand corner and lower right hand corner where the image will be drawn using the two coordinate version of _PUTIMAGE. The offset value is subtracted from the x and y location in the first coordinate pair giving the upper left hand corner coordinates. The offset value is added to the x and y location in the second coordinate pair giving the lower right hand corner coordinates.

        o% = INT(Abra(c%).size) '                                   yes, get current size of bloom image
        _PUTIMAGE (Abra(c%).x - o%, Abra(c%).y - o%)-(Abra(c%).x + o%, Abra(c%).y + o%), Abra(c%).image


After the bloom has been drawn it's time to update its values so the next time around it's a bit smaller and its position is updated based on its speed and direction. The size of the bloom is multiplied by .95 effectively making it 95% as large as it previously was. The bloom's life span is decremented by 1 with each passing frame shortening the time it has to live.

        Abra(c%).lifespan = Abra(c%).lifespan - 1 '                 decrement lifespan of bloom
        Abra(c%).size = Abra(c%).size * .95 '                       decrease size of bloom slightly


The x and y values of the bloom are multiplied by their direction and speed to compute the new location the bloom will be located at the next time around.

        Abra(c%).x = Abra(c%).x + Abra(c%).xdir * Abra(c%).xspeed ' update x position of bloom
        Abra(c%).y = Abra(c%).y + Abra(c%).ydir * Abra(c%).yspeed ' update y position of bloom


Next we check to see if the bloom has left the bottom of the screen. If it has one of two things can happen. If the BOUNCE constant variable is set to TRUE the vertical speed of the bloom is reversed causing the bloom to move upward on the screen simulating a bounce. If however the BOUNCE constant is set to FALSE the bloom's life span is immediatly set to 0 (zero) because it is no longer needed due to the fact that it would be drawn off screen.

        IF Abra(c%).y > SHEIGHT - 1 THEN '                          has bloom left bottom of screen?
            IF BOUNCE THEN '                                        should bloom bounce?
                Abra(c%).yspeed = -Abra(c%).yspeed '                yes, reverse y velocity
            ELSE '                                                  no
                Abra(c%).lifespan = 0 '                             kill it, no longer needed
            END IF
        END IF


The horizontal speed needs to be slowed down simulating drag as it moves by multiplying it by .9 effectively shrinking the value by 10% with each pass. The vertical speed needs to be decreased, which in effect will eventually stop the upward motion of the bloom and increase its speed in a downward direction, simulating gravity.

        Abra(c%).xspeed = Abra(c%).xspeed * .9 '                    decrease x velocity slightly
        Abra(c%).yspeed = Abra(c%).yspeed - .5 '                    decrease y velocity (simulating gravity)

How does decreasing the vertical speed somehow slow the upward motion to a stop and then reverse it in a downward motion at an ever increasing speed simulating gravity? Well, I'm glad you asked. Earlier in the program a vertical direction of -1 is given to all blooms that are created.

Abra(Index%).ydir = -1 '                                    vertical direction of bloom (up)

A random vertical speed, based off of the constant MAXYSPEED is then computed.

Abra(Index%).yspeed = INT(RND(1) * MAXYSPEED) '             random vertical speed of bloom

Let's say for a given bloom the random vertical speed chosen was 3. Multiplying the speed of 3 by the direction of -1 yields a value of -3, so the vertical position of the bloom will move three pixels upward on the screen when subtracted from the current vertical y coordinate location. When the POCUS subroutine is called each time .5 is subtracted from the vertical speed.

        Abra(c%).yspeed = Abra(c%).yspeed - .5 '                    decrease y velocity (simulating gravity)

This time multiplying the speed of 2.5 by the direction of -1 yields -2.5, a slightly smaller amount to move in the vertical direction. The next time around the value is 2 times -1 (-2), then 1.5 times -1 (-1.5), 1 times -1 (-1), .5 times -1 (-.5) and finally 0 (zero) times -1 (0) where the bloom stops moving in an upward direction. On the next pass the vertical speed will be -.5 and multiplying this by the direction of -1 yields the result of .5, a positive number which will start moving the bloom in a downward direction! With each pass the vertical speed becomes a larger negative number which keeps increasing the speed of the bloom downward. Remember in elementary school math class you were taught that a negative times a negative equals a positive? Well, here we are using that concept to our advantage to simulate a gravitional pull downward.

Do you see what the program did here? By using simple math it was able to simulate bouncing, drag and gravity! Nothing more than addition, subtraction and multiplication were used to ahieve these results. Once again, no need to be afraid of the perceived notion that you need to be a math genius to create impressive results in your code.

The final task that the POCUS subroutine needs to perform is to decrement the index value so the next array item can be worked on.

    c% = c% - 1 '                                                   decrement to next index in array
LOOP


Now it's your turn to have a little fun with the program. In the variable declaration section a series of constants were created to control the look and feel of the blooms.

CONST SWIDTH = 640 '       screen width
CONST SHEIGHT = 480 '      screen height
CONST BLOOMAMOUNT = 5 '    number of blooms per mouse movement (don't go too high!)
CONST MAXSIZE = 64 '       maximum size of blooms (don't go too high!)
CONST MAXLIFE = 32 '       maximum life time on screen
CONST MAXXSPEED = 6 '      maximum horizontal speed at bloom creation
CONST MAXYSPEED = 10 '     maximum vertical speed at bloom creation
CONST BOUNCE = FALSE '     set to TRUE to have blooms bounce off bottom of screen


In Task 5 the concept of constants, or variables that never change value within a program, was introduced. Constants are a great way to alter a program's look and feel easily by setting them up at the beginning of a program giving you the ability to quickly change values and see the end results. By changing the values of the constants above, and then executing the program, you get instant feedback on what different values for each will achieve. Go ahead and play with the constant values to get interesting results. Start off by setting the BOUNCE constant to TRUE to see the simulated bouncing effect off the bottom of the screen.

CONST BOUNCE = TRUE '      set to TRUE to have blooms bounce off bottom of screen

And then set the MAXLIFE constant value to 128 to see the blooms bounce a few times.

CONST MAXLIFE = 128 '      maximum life time on screen

- Getting and Putting Images With _PUTIMAGE -

The full command syntax for _PUTIMAGE is:

_PUTIMAGE (dx1%, dy1%) - (dx2%, dy2%), SourceHandle&, DestHandle&, (sx1%, sy1%) - (sx2%, sy2%)

dx1%          - the first corner x coordinate on the destination image or screen
dy1%          - the first corner y coordinate on the destination image or screen
dx2%          - the second corner x coordinate on the destination image or screen
dy2%          - the second corner y coordinate on the destination image or screen
SourceHandle& - the source image handle to be placed on the current destination image or screen

DestHandle&   - the destination image handle where the source image handle is to be placed
sx1%          - the first corner x coordinate on the source image
sy1%          - the first corner y coordinate on the source image
sx2%          - the second corner x coordinate on the source image
sy2%          - the second corner y coordinate on the source image


In the two coordinate version of _PUTIMAGE discussed earlier DestHandle& and the source coordinate pair (sx1%, sy1%) - (sx2%, sy2%) were ommitted which means their default values were used instead. The default value for DestHandle& will either be 0 (zero) which is the current screen or the destination image that was set using the _DEST statement. The source coordinate pair, (sx1%, sy1%) - (sx2%, sy2%), if ommitted will default to the entire source image, or (0, 0) - (_WIDTH -1, _HEIGHT -1), placing the entire image on the destination as you saw happen with the previous bee image examples.

However, _PUTIMAGE gives us control of where the image should be placed through the use of DestHandle& and which portion of the source image should be grabbed through the use of the source coordinate pair (sx1%, sy1%) - (sx2%, sy2%). Here is a snippet of code showing this behavior in action.

DIM AllFlags& ' image containing animations of flag

AllFlags& = _LOADIMAGE(".\GFX\flags.png", 32) '                      load image into RAM
SCREEN _NEWIMAGE(256, 199, 32) '                                     create graphics screen
CLS , _RGB32(255, 255, 255) '                                        clear screen with white background
_PUTIMAGE (0, 0)-(255, 198), AllFlags&, _DEST, (0, 0)-(255, 198) '   display portion of flags image
SLEEP '                                                              wait for keystroke
CLS , _RGB32(255, 255, 255) '                                        clear screen with white background
_PUTIMAGE (0, 0)-(255, 198), AllFlags&, _DEST, (256, 0)-(511, 198) ' display another portion of flags image
SLEEP '                                                              wait for keystroke

First _PUTIMAGE
Figure 16 - The first _PUTIMAGE statement at work

The AllFlags& image contains eight pictures of the U.S. flag in various waving positions with each being 256x199 pixels in size. The first _PUTIMAGE statement, as described in Figure 16 above, has been instructed to grab a portion of the AllFlags& image, designated as the source, and place that portion onto the screen, designated as the destination of _DEST.

Second _PUTIMAGE
Figure 17 - The second _PUTIMAGE statement at work

Once again the second _PUTIMAGE statement, as described above in Figure 17, has been instructued to grab a portion of the AllFlags& image and place it to the current destination of _DEST which is our SCREEN. The first coordinate pair in _PUTIMAGE is always pointing where on the destination image or screen to place the source image. The second coordinate pair in _PUTIMAGE is always pointing to which area of the source image to grab.

_PUTIMAGE isn't just restricted to placing images, or portions of images, on the screen. You can specify a destination image sitting in RAM waiting to be used as this example program shows.

DIM AllFlags& ' image containing animations of flag
DIM Flags&(7) ' array containing individual frames of flag animation
DIM Count% '    generic counter
DIM Banner& '   Star Spangled Banner MIDI music

AllFlags& = _LOADIMAGE(".\GFX\flags.png", 32) '    load image into RAM
Banner& = _SNDOPEN(".\SND\banner.mid") '           load patriotic music
DO '                                               start image load loop
    Flags&(Count%) = _NEWIMAGE(256, 199, 32) '     create top row flag image holder
    Flags&(Count% + 4) = _NEWIMAGE(256, 199, 32) ' create bottom row flag image holder
    '*
    '* Get top row flag and place in appropriate Flags&() image
    '*
    _PUTIMAGE (0, 0)-(255, 198), AllFlags&, Flags&(Count%), (Count% * 256, 0)-(Count% * 256 + 255, 198)
    '*
    '* Get bottom row flag and place in appropriate Flags&() image
    '*
    _PUTIMAGE (0, 0)-(255, 198), AllFlags&, Flags&(Count% + 4), (Count% * 256, 199)-(Count% * 256 + 255, 397)
    Count% = Count% + 1 '                          increment to next array index
LOOP UNTIL Count% = 4 '                            leave loop when count exceeded
SCREEN _NEWIMAGE(256, 199, 32) '                   create graphics screen
_TITLE "All rise for our national anthem" '        give window a title
_SCREENMOVE _MIDDLE '                              move screen to middle of desktop
Count% = 0 '                                       reset array index counter
_SNDPLAY Banner& '                                 play patriotic music
DO '                                               begin flag waving loop
    CLS , _RGB32(255, 255, 255) '                  clear screen with white background
    _LIMIT 10 '                                    10 frames per second
    _PUTIMAGE (0, 0), Flags&(Count%) '             place flag image on screen
    _DISPLAY '                                     update screen with changes
    Count% = Count% + 1 '                          increment array index counter
    IF Count% = 8 THEN Count% = 0 '                keep index within limits
LOOP UNTIL _KEYDOWN(27) '                          leave loop when ESC pressed
FOR Count% = 0 TO 7 '                              cycle through all array indexes
    _FREEIMAGE Flags&(Count%) '                    free flag images from RAM (cleanup)
NEXT Count%
_SNDCLOSE Banner& '                                free music from RAM (cleanup)
SYSTEM '                                           return to Windows

All rise
Figure 18 - All Rise

Using _PUTIMAGE to pull a series of images from another image and store them in RAM for later use is very common in game programming. The images being pulled are called sprites and the image that contains the many images being pulled is known as a sprite sheet. It's an efficient way to store many images within one image file. We'll discuss more about sprites, sprite sheets and their use in Task 18.

The waving flag example code above shows how _PUTIMAGE can be used to move image information from one image contained in RAM to another image contained in RAM instead of the default screen or image.


    _PUTIMAGE (0, 0)-(255, 198), AllFlags&, Flags&(Count%), (Count% * 256, 0)-(Count% * 256 + 255, 198)
    _PUTIMAGE (0, 0)-(255, 198), AllFlags&, Flags&(Count% + 4), (Count% * 256, 199)-(Count% * 256 + 255, 397)

These lines of code grab a portion of the AllFlags& image that was previously loaded into RAM and places it into one of the Flags&() images that were also previously created in RAM. If you have ever wondered what is happening when you start a game and you have to wait for "Loading Graphics..." this is most likely what is going on behind the scenes as you wait.

These lines of code will do the same thing as the example lines shown above.


    _PUTIMAGE , AllFlags&, Flags&(Count%), (Count% * 256, 0)-(Count% * 256 + 255, 198)
    _PUTIMAGE , AllFlags&, Flags&(Count% + 4), (Count% * 256, 199)-(Count% * 256 + 255, 397)

Notice how these lines of code has ommitted the destination coordinates of (0, 0)-(255, 198)? Just like with the source coordinates, if the destination coordinates are ommitted the default used is the actual size of the destination image (0, 0)-(_WIDTH - 1, _HEIGHT - 1). These lines of code instruct _PUTIMAGE to place whatever portion of the source image that was grabbed and place it over top of the destination in the upper left hand corner coordinate (0, 0). Since we created the array of images the exact same size as the portion of the source images we are grabbing:

    Flags&(Count%) = _NEWIMAGE(256, 199, 32) '     create top row flag image holder
    Flags&(Count% + 4) = _NEWIMAGE(256, 199, 32) ' create bottom row flag image holder


the images when placed in the destination is a perfect fit, so there is no reason to supply destination coordinates. The _PUTIMAGE statement takes some time to master and I'm sure the _PUTIMAGE Wiki page will be your best friend until you do.

- A Few More Useful Graphics Commands -

There are many graphics statements and functions available to the QB64 programmer. This task is aimed at covering the most often used and useful to the game programmer. For a complete listing of all the graphics statements and functions available make sure to visit the graphics command section of the QB64 Wiki here.

- _COPYIMAGE( ) -

The _COPYIMAGE() function does exactly what its name implies, it makes a copy of an image or screen.

newhandle& = _COPYIMAGE(imagehandle&)

newhandle&   - the handle of the copied image
imagehandle& - the handle of the image being copied


DIM Image& '     image to be copied
DIM ImageCopy& ' copy of image

Image& = _NEWIMAGE(100, 100, 32) '           create 100x100 image
SCREEN _NEWIMAGE(640, 480, 32) '             enter graphics screen
CLS '                                        clear screen
_DEST Image& '                               set destination to image
CIRCLE (50, 50), 40, _RGB32(255, 255, 255) ' draw a white circle
ImageCopy& = _COPYIMAGE(Image&) '            copy the image of white circle
CIRCLE (50, 50), 30, _RGB32(255, 255, 0) '   draw yellow circle on image
_DEST 0 '                                    set destination to screen
_PUTIMAGE (10, 10), Image& '                 show image
_PUTIMAGE (110, 10), ImageCopy& '            show copied image before yellow circle

This program creates a small image of two circles, a yellow one inside of a white one. However, before the yellow circle is drawn the image is copied so the copy will only contain the white circle. Something like this could come in handy as an undo feature if you're creating a graphics editing program.

- _AUTODISPLAY and _DISPLAY -

By now I'm sure you've noticed that every code example in this and previous tasks that have had some sort of movement uses the _DISPLAY statement. Before we get into the statement's use we need to dicsuss how montitors work. Monitors have what is known as a refresh rate which means how many times per second they update the image on the screen. Typical LCD, LED and CRT monitors have a refresh rate of 60Hz to 120Hz or 60 to 120 times per second. High end monitors will support 240Hz or more, especially ones that support 3D glasses. The more times you can update the screen per second the less flicker that is created making for much smoother motion and less eye strain on the viewer. A refresh (or redraw) of the screen is done by starting at the upper left corner pixel and moving right across the screen one pixel at a time until the right side of the screen is encountered. The electronics then move down to the next pixel on the left hand side and do it again. In the case of a 1080p monitor this is done 1080 times before the screen is fully refreshed, since 1080p monitors contain 1080 vertical pixels.

Let's say the programmer issues a command to draw something to the screen while the refesh happens to be in the middle of the screen. This means the bottom half of the image will be drawn, then the top half, making a noticeable "tear" in the image as it is being drawn causing flickering effects and the ability to see the command drawing. The human eye can pick up on this and it looks quite disturbing.


DIM x%
DIM y%
DIM xdir%
DIM ydir%

SCREEN _NEWIMAGE(640, 480, 32)
CLS
x% = 319
y% = 239
xdir% = 1
ydir% = 1
DO
    _LIMIT 120
    CLS
    CIRCLE (x%, y%), 50, _RGB32(255, 255, 255)
    PAINT (x%, y%), _RGB32(255, 255, 255), _RGB32(255, 255, 255)
    x% = x% + xdir%
    IF x% = 24 OR x% = 614 THEN xdir% = -xdir%
    y% = y% + ydir%
    IF y% = 24 OR y% = 454 THEN ydir% = -ydir%
LOOP UNTIL _KEYDOWN(27)

Yikes! Am I right? The _DISPLAY statement watches the video card hardware for the optimal time to update the image on the screen, ensuring that refreshes are always done from the top to the bottom on screen updates. Adding the _DISPLAY statement after your graphics commands have finished makes a WORLD of difference.

DIM x%
DIM y%
DIM xdir%
DIM ydir%

SCREEN _NEWIMAGE(640, 480, 32)
CLS
x% = 319
y% = 239
xdir% = 1
ydir% = 1
DO
    _LIMIT 120
    CLS
    CIRCLE (x%, y%), 50, _RGB32(255, 255, 255)
    PAINT (x%, y%), _RGB32(255, 255, 255), _RGB32(255, 255, 255)
    x% = x% + xdir%
    IF x% = 24 OR x% = 614 THEN xdir% = -xdir%
    y% = y% + ydir%
    IF y% = 24 OR y% = 454 THEN ydir% = -ydir%
    _DISPLAY '                                    added to remove flicker
LOOP UNTIL _KEYDOWN(27)

Now, there are a few down sides to using _DISPLAY. Firstly, loops containing graphics commands and _DISPLAY are slowed down considerably. Remove the _LIMIT 120 line from each of the above code examples to see just how much. Secondly, _DISPLAY acts like a switch that tells QB64 to only perform screen updates when the _DISPLAY statement is issued. Enter the following code to see this happen.

SCREEN _NEWIMAGE(640, 480, 32) '                enter graphics screen
CLS '                                           clear the screen
CIRCLE (319, 239), 200, _RGB32(255, 255, 255) ' draw white circle
_DISPLAY '                                      update screen with changes
CIRCLE (319, 239), 100, _RGB32(255, 255, 0) '   this yellow circle not seen
SLEEP '                                         until a key is pressed for one last update

The first time _DISPLAY is used you must use it every time you want updates to be seen on the screen. Inside of a loop where screen changes are made continuously this is usually not a problem. But as the code example above shows, the yellow circle did not get drawn until the program ended, where one final update of the screen is done automatically. If you are having trouble with graphics commands not drawing to the screen the first place to look is the _DISPLAY statement placements in your code. Chances are you have a _DISPLAY somewhere causing QB64 to wait for additional _DISPLAY statement updates.

To set screen updates back to normal use the _AUTODISPLAY statement. _AUTODISPLAY turns off any previous _DISPLAY statement behaviors.


- _FULLSCREEN -

The games you create with QB64 don't have to be limited to the size of the SCREEN window you create. The _FULLSCREEN statement allows your window to be stretched and occupy the entire player's screen in two different ways.

_FULLSCREEN [ _OFF | _STRETCH | _SQUAREPIXELS ]

_OFF          - turns off full screen mode if currently activated
_STRETCH      - the image will be stretched to fit the screen perfectly
_SQUAREPIXELS - the image will be enlarged to either full width or full height, whichever fits first

Using _FULLSCREEN by itself will activate full screen mode and QB64 will choose whether to use the _STRETCH or _SQUAREPIXELS method of enlarging the image based on the image and screen resolutions.

'--------------------------------
'- Variable Declaration Section -
'--------------------------------

DIM x% '     x location of circle
DIM y% '     y location of circle
DIM xdir% '  x direction of circle
DIM ydir% '  y direction of circle

'----------------------------
'- Main Program Begins Here -
'----------------------------

SCREEN _NEWIMAGE(640, 480, 32) '                 enter graphics mode
x% = 319 '                                       set x coordinate of circle
y% = 239 '                                       set y coordinate of circle
xdir% = 1 '                                      set x direction of circle
ydir% = 1 '                                      set y direction of circle
DO '                                             begin main loop
    _LIMIT 120 '                                 120 frames per second
    CLS '                                        clear screen
    CIRCLE (x%, y%), 50, _RGB32(255, 255, 255) ' draw filled circle
    PAINT (x%, y%), _RGB32(255, 255, 255), _RGB32(255, 255, 255)
    LOCATE 2, 1 '                                position cursor
    PRINT " Press ENTER to toggle FULL SCREEN mode or ESC to exit."
    x% = x% + xdir% '                            increment x location
    IF x% = 24 OR x% = 614 THEN xdir% = -xdir% ' reverse x direction if needed
    y% = y% + ydir% '                            increment y location
    IF y% = 24 OR y% = 454 THEN ydir% = -ydir% ' reverse y direction if needed
    _DISPLAY '                                   update screen with changes
    IF INKEY$ = CHR$(13) THEN '                  did user press ENTER?
        IF _FULLSCREEN THEN '                    yes, already in full screen mode?
            _FULLSCREEN _OFF '                   yes, turn full screen off
        ELSE '                                   no, not in full screen mode
            GOFULLSCREEN '                       go to full screen if possible
        END IF
    END IF
LOOP UNTIL _KEYDOWN(27) '                        exit loop if ESC key pressed
SYSTEM '                                         return to Windows

'-----------------------------------
'- Function and Subroutine section -
'-----------------------------------

SUB GOFULLSCREEN ()

'*
'* Asks the user to verify that full screen mode was activated correctly.
'* If full screen mode failed normal operation will resume after 10 seconds.
'*

DIM Count% '    generic counter
DIM KeyPress$ ' any key user presses

_AUTODISPLAY '                                   turn _DISPLAY off
CLS '                                            clear screen
LOCATE 12, 1 '                                   position cursor
PRINT " Attempting to enter FULL SCREEN mode. If the screen fails to appear simply"
PRINT " wait 10 seconds and this screen will reappear. Press any key to continue.."
DO: LOOP UNTIL INKEY$ <> "" '                    wait for a keystroke
CLS '                                            clear screen
_FULLSCREEN '                                    enter full screen mode
LOCATE 12, 1 '                                   position cursor
PRINT "            Press the X key to confirm FULL SCREEN is active..."
DO '                                             begin 10 second loop
    _LIMIT 1 '                                   1 loop per second
    Count% = Count% + 1 '                        increment loop counter
    KeyPress$ = INKEY$ '                         get key user presses
    IF UCASE$(KeyPress$) = "X" THEN EXIT SUB '   if X was pressed exit subroutine
LOOP UNTIL Count% = 10 '                         stop loop after 10 counts
_FULLSCREEN _OFF '                               didn't work, exit full screen mode

END SUB

As the example program above illustrates, it's always best to ask the user to verify that the full screen action was performed correctly. Not all video cards are created equally as a few may fail to execute full screen properly, especially the really cheap built in adapters found on today's inexpensive motherboards. Some of my QB64 games have made the circuit on the Internet and I've gotten a few reports back where full screen failed to work on player's systems.

_FULLSCREEN can also be used a function to return the current state of _FULLSCREEN as this line in the above program illustrates:


        IF _FULLSCREEN THEN '                    yes, already in full screen mode?

_FULLSCREEN will return a value of 0 (zero) if not in full screen mode, a value of 1 if in _STRETCH mode and a value of 2 if in _SQUAREPIXELS mode.

In Task 8 when covering the CIRCLE command the concept of aspect ratio was introduced. When using the _FULLSCREEN statement you need to consider the aspect ratio of the screen or image you wish to make full screen. Most monitors today have a 16:9 aspect ratio, meaning that for every 16 pixels horizontal there are 9 pixels vertical. Common aspect ratios over the past 10 years used in computer monitors have been:


RatioDecimalCommon screen graphics mode dimensions found over the years for each ratio.
16:101.6320x200640x4001280x8001440x9001680x10501920x12002560x1600
4:31.33320x240640x480800x6001024x7681280x9601400x10501600x12002048x1536
5:41.251280x10242560x2048
16:91.77854x4801024x5761280x7201366x7681600x9001920x10802560x1440
Source
Chart 5 - Monitor Aspect Ratios and Screen Resolutions

Matching a game screen's aspect ratio to the aspect ratio of the player's monitor makes for a much better experience at full screen. This is why many games offer a myriad of screen resolutions in their options to choose from, allowing the player to choose the actual resolution of the screen or a host of resolutions that match the screen's aspect ratio. Here is a program that shows one method of determining the player's screen resolution which in turn gives us the aspect ratio.

DIM Desktop& '       image of the desktop
DIM DesktopWidth% '  width of desktop
DIM DesktopHeight% ' height of desktop
DIM Ratio% '         aspect ratio of screen

Desktop& = _SCREENIMAGE '                            grab image of desktop
DesktopWidth% = _WIDTH(Desktop&) '                   get desktop image width
DesktopHeight% = _HEIGHT(Desktop&) '                 get dekstop image height
_FREEIMAGE Desktop& '                                image of desktop no longer needed
Ratio% = FIX(DesktopWidth% / DesktopHeight% * 10) '  convert ratio to 2 digit value
PRINT '                                              display info to user
PRINT " Screen Ratio Finder"
PRINT " -------------------"
PRINT
PRINT " Your desktop dimensions are"; DesktopWidth%; "x"; DesktopHeight%
PRINT
PRINT " This translates to a screen ratio of ";
SELECT CASE Ratio% '                                 which ratio was computed?
    CASE 12 '                                        1.2 (1.25)
        PRINT "5:4 or 1.25"
    CASE 13 '                                        1.3 (1.33333..)
        PRINT "4:3 or 1.33"
    CASE 16 '                                        1.6
        PRINT "16:10 or 1.6"
    CASE 17 '                                        1.7 (1.77777..)
        PRINT "16:9 or 1.77"
    CASE ELSE '                                      not a standard ratio!
        PRINT "unknown or"; FIX(DesktopWidth% / DesktopHeight% * 100) / 100
END SELECT

- _SCREENIMAGE -

In the previous program example you saw the use of _SCREENIMAGE to grab a snapshot of the desktop so its width and height could be measured.

Desktop& = _SCREENIMAGE '                            grab image of desktop

_SCREENIMAGE can be used in the following manner:

handle& = _SCREENIMAGE [(x1%, y1%, x2%, y2%)]

handle& - the long integer handle value of the returned image
x1% - the upper left x coordinate to start at
y1% - the upper left y coordinate to start at
x2% - the lower right x coordinate to end at
y2% - the lower right y coordinate to end at

Using _SCREENIMAGE by itself results in the entire desktop image being returned in handle&. The screen coordinates are optional but if provided allows _SCREENIMAGE to grab a portion of the desktop to return as an image.


DIM Desktop& ' the partial image of the desktop

Desktop& = _SCREENIMAGE(0, 0, 639, 479) ' grab the upper left 640x480 area of desktop

SCREEN Desktop& '                         display the grabbed portion of desktop

The image returned by _SCREENIMAGE will always be a 32 bit color image.

- _SCREENMOVE, _SCREENX and _SCREENY -

The _SCREENMOVE statement allows you to move the program window around on the user's desktop.

_SCREENMOVE {x%, y% | _MIDDLE}

x%      - the x coordinate to move the window to on the desktop
y%      - the y coordinate to move the window to on the desktop
_MIDDLE - the window will be centered on the desktop

You must supply _SCREENMOVE with either an x,y desktop coordinate or the _MIDDLE directive but not both at the same time.


DIM KeyPress$ ' any key the user presses

SCREEN _NEWIMAGE(640, 480, 32) '  enter a graphics screen
_SCREENMOVE _MIDDLE '             move screen to middle of desktop
LOCATE 2, 1 '                     position cursor
PRINT " _SCREENMOVE Demo" '       display instructions to user
PRINT " ----------------"
PRINT
PRINT " Use the mouse to drag this window around."
PRINT
PRINT " Press ENTER to center the window."
PRINT " Press SPACEBAR to move window to upper left of desktop."
PRINT
PRINT " Current X location on desktop:"
PRINT " Current Y location on desktop:"
PRINT
PRINT " Press ESC to leave this demo."
DO '                              start main loop
    _LIMIT 30 '                   30 loops per second
    LOCATE 10, 32 '               position cursor
    PRINT _SCREENX; "  " '        report x location of window
    LOCATE 11, 32 '               position cursor
    PRINT _SCREENY; "  " '        report y location of window
    KeyPress$ = INKEY$ '          get any user key stroke
    SELECT CASE KeyPress$ '       which key was struck?
        CASE CHR$(13) '           ENTER key
            _SCREENMOVE _MIDDLE ' move screen to middle of desktop
        CASE CHR$(32) '           SPACEBAR key
            _SCREENMOVE 0, 0 '    move screen to upper left corner
    END SELECT
LOOP UNTIL _KEYDOWN(27) '         leave loop when ESC pressed
SYSTEM '                          return to Windows

_SCREENX returns the window's current x coordinate location on the screen and _SCREENY returns the window's current y coordinate location on the screen.

    LOCATE 10, 32 '               position cursor
    PRINT _SCREENX; "  " '        report x location of window
    LOCATE 11, 32 '               position cursor
    PRINT _SCREENY; "  " '        report y location of window







-- Your Turn --










-- COMMAND REFERENCE --


Commands learned in previous tasks:

PRINT
INPUT
DIM
REM or '
CONST
TIME$
VAL()
IF...THEN
AND
END
GOTO
SELECT CASE...END SELECT
CASE
TO
IS
CASE ELSE
colon ( : )
FOR...NEXT
STEP
CLS
SLEEP
_DELAY
SYSTEM
DO...LOOP UNTIL (and variations)
WHILE...WEND
SCREEN
LINE
CIRCLE
PAINT
PSET
SUB
END SUB
FUNCTION
END FUNCTION
SHARED
SQR()
^ (exponent)
INPUT$
LINE INPUT
INKEY$
CHR$()
_KEYDOWN
_KEYHIT
_MOUSEX
_MOUSEY
_MOUSEINPUT
_MOUSEHIDE
_MOUSESHOW
_MOUSEMOVE
_MOUSEBUTTON
DIM
TYPE
END TYPE
AS
UCASE$()
LCASE$()
LTRIM$()
RTRIM$()
INSTR()
STR$()
DATE$
LEFT$()
RIGHT$()
MID$()
ASC()
STRING$()
SPACE$()
SWAP
AND
OR
XOR
NOT
\ (integer division)
MOD
INT()
CINT()
CLNG()
CSNG()
CDBL()
_ROUND()
FIX()
ABS()
SGN()
SIN()
COS()
TAN()
ATN()
_DIREXISTS
_FILEEXISTS
CHDIR
MKDIR
RMDIR
KILL
NAME ... AS
OPEN
INPUT (file mode)
OUTPUT (file mode)
APPEND (file mode)
BINARY (file mode)
RANDOM (file mode)
PRINT (file statement)
CLOSE
EOF()
INPUT (file statement)
REDIM
_PRESERVE
UBOUND()
WRITE (file statement)
LINE INPUT (file statement)
FREEFILE
LOF()
SHELL
FILES
_HIDE
BEEP
SOUND
PLAY
_SNDOPEN()
_SNDPLAY
_SNDPLAYCOPY
_SNDLOOP
_SNDPLAYFILE
_SNDPLAYING()
_SNDSTOP
_SNDVOL
_SNDPAUSE
_SNDPAUSED()
_SNDLIMIT
_SNDLEN()
_SNDSETPOS
_SNDGETPOS()
_SNDCLOSE

New commands introduced in this task:

_NEWIMAGE()
_LOADIMAGE()
_WIDTH()
_HEIGHT()
_PUTIMAGE
_SETALPHA
_RGB()
_RGB32()
&H
_DEST
_SOURCE
_FREEIMAGE
_COPYIMAGE()
_DISPLAY
_AUTODISPLAY
_FULLSCREEN
_SCREENIMAGE
_SCREENMOVE
_SCREENX
_SCREENY

Concepts learned in previous tasks:

execute
statement
expression
literal string
syntax error
variables
type declarations
literal strings
concatenation
integers
long integers
single precision
double precision
strings
null strings
reserved words
operators ( +, -, *, / )
declare
constant
relational operators
functions
nesting
order of operations
Boolean
conditions
indenting
loops
labels
frame rate
controlled loops
conditional loops
Monochrome
Grayscale
pixels
bits per pixel
radian
counter-clockwise
aspect ratio
algorithm
subroutine
function
local variables
RAM
global variables
Pythagorean Theorem
BIOS
ASCII
buffer
CMOS
ROM
ASCII chart
array
one dimensional array
element
index
two dimensional array
three dimensional array
parse
geometry
algebra
trigonometry
calculus
angular momentum
physics engine
bitmap
polygon
vector math
numbering system
transistor
binary
binary numbering system
memory address
George Boole
Boolean logic
logic gate
bitwise math
flags
Banker's Rounding
sine
radian
cosine
tangent
arctangent
sequential file
dynamic array
Comma Separated Value (CSV) file
records
database
FM synthesis
MIDI
audiophile
WAV
OGG
AIF
RIF
VOC
MID
MOD
MP3

New concepts introduced in this task:

BMP

JPG
PNG
GIF
PNM
XPM
XCF
PCX
TIF
LBM
TGA
lossless compression
Flash
alpha channel
Parallax scrolling
parallax
color depth
1 bit color
monochrome
CGA
EGA
VGA
unsigned long integer
Hexadecimal
particle effects
sprites
sprite sheet
refresh rate
LCD
LED
CRT
Hz
1080p
aspect ratio (as it applies to monitors)