Lesson 14: Colors and Transparency

Computers and Color

In order to understand how color is used in a computer we must first take a short journey through the history of computers and color. The very earliest computer screens were nothing more than oscilloscopes that traced a beam of light onto a phosphor coating within an electron tube. The tracing was so fast however that a human's eye could not see it and due to the phosphor coating glowing for a short time afterward coupled with persistence of vision images and text could be drawn onto the tube. Looking back at the very earliest computer screens you'll notice that they are round because of their oscilloscope roots.

These early screens offered a color depth of 1 bit. Since a single bit can only hold two values, 0 and 1, the screen was either off at certain locations (0) or on at other locations (1). As electron tubes progressed over the years their round shape gave way to more rectangular shapes creating the common Cathode Ray Tube (CRT) computer screens from the 1970s to early 2000s. The earliest form of color for these CRT screens was known as monochrome.

Monochrome screens typically displayed white, green, or orange text and graphics. They could only produce one color, the same as the old oscilloscope tubes, so they offered a color depth of 1 bit. They could also address each individual pixel on the screen. To turn on a pixel a programmer would place a 1 at the pixel's address. To turn the pixel off a 0 would be placed at the pixel's address. It was a crude form of color but was very popular for applications that did not need actual colors, such as cash register terminals and word processing stations. By the late 70's computers capable of creating actual colors began to appear. Instead of going over each and every computer at the time we'll focus on the progression of the IBM PC and its use of color.

The first color video graphics cards available were known as CGA (Color Graphics Adapter). They were capable of up to 16 colors in text mode and 4 colors in graphics mode. CGA did this by mixing Red, Green, and Blue (RGB) values together to achieve the desired color. CGA has a color depth of 4 bits per pixel, 1 bit for red, 1 bit for green, 1 bit for blue, and 1 bit for intensity.  This can be represented as a binary value of 4 bits:

INTENSITY  RED  GREEN  BLUE
    1       1     1     1   = WHITE     (15) (by mixing all colors and making intense)
    0       1     0     1   = MAGENTA   (5)  (by mixing red and blue)
     1       1     0     0   = LIGHT RED (12) (red bit on with intensity)
    0       0     1     1   = CYAN      (3)  (by mixing blue and green)

The following example program shows what was available when CGA was the only video adapter available for the PC.

( This code can be found at .\tutorial\Lesson14\CGADemo.bas )

DIM c% ' color counter
DIM n$ ' color names

PRINT
PRINT
" The 16 CGA text colors" '               Demo program showing what is what like to create
PRINT " ----------------------" '               games back in the early 80s using the CGA video
PRINT '                                         adapter.
FOR c% = 0 TO 15
   READ n$
   COLOR c%
   PRINT " This is color number ->";
   COLOR 7
   PRINT c%; "("; n$; ")"
NEXT c%
PRINT
PRINT
" Notice how 8 through 15 is a repeat of 0 through 7."
PRINT " 8 through 15 simply have an intensity bit turned on."
SLEEP
SCREEN 1 ' 320x200 4 color graphics mode
FOR c% = 0 TO 3
   LINE (c% * 80, 0)-(c% * 80 + 79, 199), c%, BF
NEXT c%
LOCATE 8, 8: PRINT " CGA SCREEN 1 - 4 COLORS "
LOCATE 12, 3: PRINT "BLACK"
LOCATE 12, 13: PRINT " CYAN "
LOCATE 12, 21: PRINT " MAGENTA "
LOCATE 12, 32: PRINT " WHITE "
LOCATE 16, 13: PRINT " 320x200 PIXELS "
LOCATE 20, 5: PRINT " IMAGINE MAKING GAMES IN THIS! "
SLEEP
DATA "Black","Blue","Green","Cyan","Red","Magenta","Brown (or Orange)","Light Gray"
DATA "Dark Gray","Light Blue","Light Green","Light Cyan","Light Red","Light Magenta","Yellow","White"

Figure 1: The CGA text mode (SCREEN 0)

Figure 2: The CGA graphics mode (SCREEN 1)

Later video adapters followed to include the Enhanced Graphics Adapter (EGA) which was capable of truly being able to display 16 colors at a time in graphics mode for a true color depth of 4 bits. The introduction of the Video Graphics Array (VGA) adapter took this even further being able to display up to 256 colors at a time for a color depth of 8 bits. The following example program displays the VGA 256 default color palette.

( This code can be found at .\tutorial\Lesson14\VGADemo.bas )

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 VGA screen
CLS '                                              clear the screen
c% = 0 '                                           reset color
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 '                               place text cursor
       PRINT c%; '                                 print color value
        c% = c% + 1 '                              increment color value
       _DELAY .0125 '                              short pause
   NEXT x%
NEXT y%
SLEEP '                                            wait for key stroke
SYSTEM '                                           return to operating system

Figure 3: VGA 256 default color palette

Later advancements in video technology had their unique names such as SVGA and XGA but they all followed the same RGB ordering of bits as CGA by simply adding more bits available to the red, green, and blue color components. Today 32bit color is the norm offering 8 bits of red, 8 bits of green, 8 bits of blue, and 8 bits for an alpha transparency channel that can all be represented as a 32bit binary string of 0s and 1s.

 TRANSPARENCY          RED             GREEN            BLUE
1 1 1 1 1 1 1 1  1 1 1 1 1 1 1 1  1 1 1 1 1 1 1 1  1 1 1 1 1 1 1 1 = WHITE
1 1 1 1 1 1 1 1  1 1 1 1 1 1 1 1  0 0 0 0 0 0 0 0  1 1 1 1 1 1 1 1 = MAGENTA
1 1 1 1 1 1 1 1  1 1 1 1 1 1 1 1  0 0 0 0 0 0 0 0  0 0 0 0 0 0 0 0 = LIGHT RED
1 1 1 1 1 1 1 1  0 0 0 0 0 0 0 0  1 1 1 1 1 1 1 1  1 1 1 1 1 1 1 1 = CYAN

There are 256 levels (8 bits) of each color available in 32bit color depth (0 through 255). This is why there are 16.7 million different variations of color available. 256 times 256 times 256 = 16,777,216 or 24 bits = 224 = 16,777,216. The final 8 bits are used for transparency allowing a color to be completely clear (0 or 00000000) to fully opaque (255 or 11111111). There are in fact so many colors in the 32bit color depth palette that they can't all be displayed on a 1920x1080 wide screen monitor. There's only 2,073,600 pixels on the monitor, not nearly enough for 16 million+ unique colors!

32bit colors in QB64 are always returned or set as an unsigned long integer. Remember that unsigned long integers fall within the range of 0 to 4,294,967,295 (close to 4.3 billion) which is the same as 232. By default the transparency bits are always set to 1 meaning that the color black in binary is 11111111000000000000000000000000 or 4,278,190,080 all the way to white 11111111111111111111111111111111 or 4,294,967,295. This is why an unsigned long value needs to be used to store colors. Many times new programmers will mistake the value of 0 for black when in fact 0 is transparent black and wonder why black did not show up where they thought it should.

The _RGB32 and _RGB Statements

In its original syntax the _RGB32 statement gives the programmer an easy way of selecting a color from the 16.7 million available. A value of 0 through 255 needs to be supplied for each of the red%, green%, and blue% components of the color.

MyColor~& = _RGB32(red%, green%, blue%) '         _RGB32 variant 1

This will return a 32 bit unsigned long value contained in MyColor~& representing the chosen color. The color returned by this statement variant will always be solid, or opaque, meaning that the transparency level is always set to 255.

The _RGB32 statement can also be used to manipulate the transparency level using this syntax:

MyColor~& = _RGB32(red%, green%, blue%, alpha%) ' _RGB32 variant 2

Setting alpha% to a value of 0 returns a completely transparent color while supplying alpha% with a value of 255 creates a completely opaque color. Alpha% values in between 0 and 255 are simply varying levels of transparency (more on transparency below).

A third way to use the _RGB32 statement is to supply a single color component for grayscale shades:

MyColor~& = _RGB32(intensity%) '                  _RGB32 variant 3

The intensity% value can range from 0 to 255 and represents a shade of gray. The intensity% value is used for all three color components by the statement. For instance, dark gray is typically created using _RGB32(63, 63, 63) however by simply supplying the value of 63, _RGB32(63), it's assumed all three color components are the same value.

The final method to use _RGB32 is to control the transparency level of grayscale shades:

MyColor~& = _RGB32(intensity%, alpha%) '          _RGB32 variant 4

Once again a value supplied to alpha% from 0 to 255 will give the grayscale shade complete transparency (0) or up to complete opaqueness (255).

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 when you learned that mixing blue and yellow crayons magically created green or mixing red and blue together made purple. Because there are so many colors to choose from many programs offer what's called a color wheel or color picker as seen in Figure 4 below.

Figure 4: Examples of a color picker and wheel

The QB64 IDE will also help you choose colors with its own version of a color picker. When you start typing in the statement _RGB32( a prompt appears allowing you to press SHIFT-ENTER to select a color through the use of slider bars as seen in Figure 5 below.

Figure 5: The IDE RGB color mixer

The _RGB statement handles colors differently depending on the bit depth of the image or screen it is working with. If an image has a 32bit color depth _RGB will behave exactly the same as _RGB32 and return a true 32bit color value. However, if the image being worked on has an 8bit color depth, or 256 colors, _RGB will return a value from 0 to 255 that as closely approximates the 32bit color as possible. Here is an example program that shows that difference in real time.

( This code can be found at .\tutorial\Lesson14\ColorPick.bas )

'** _RGB32 vs _RGB demo

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
Red% = 127
Green% = 127
Blue% = 127
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

Figure 6: Playing with colors

As the example program shows when in 32bit color mode the colors smoothly change as the values for red, green, and blue are changed. However, when you switch to 8bit color mode the color in the circle somewhat abruptly changes as the values are increased and decreased.

Many locations on the Internet have common HTML color names that are used when creating web sites. Here the w3schools.com website has a nice listing of them. At RapidTables.com is a listing of the colors along with an RGB color picker you can use. However, they list the colors in a rather peculiar way. For instance, the first few colors listed at w3schools.com are:

AliceBlue    - #F0F8FF   This is AliceBlue
AntiqueWhite - #FAEBD7   This is AntiqueWhite
Aqua         - #00FFFF   This is Aqua
Aquamarine   - #7FFFD4   This is Aquamarine

The curious string of letters and numbers after the pound sign (#) are known as hexadecimal numbers. If you look back at Figure 4 the color picker offers a box in the lower right hand corner to enter a "hex" value. Most color pickers will also show you the R, G, and B values from 0 to 255 when you type in a hexadecimal number in the field. Even the previous example program displays the hexadecimal numbers.

Hexadecimal is another counting system that is used by computers. It's used as a method of shorthand for binary values. Instead of remembering that the value of 65,535 = 1111111111111111 one can simply use the hexadecimal value of FFFF to represent that.

It's not a requirement that you understand how to count in hexadecimal to get a start in game programming. Somewhere down the line though you are going to encounter code that uses it and be at a disadvantage compared to other coders. QB64 can even convert from decimal to hexadecimal and back for you with built in commands. If you are unfamiliar with hexadecimal I highly suggest you view a tutorial on its use. A very good tutorial can be found here at SparkFun's web site.

Hexadecimal is a BASE16 counting system that uses the numbers 0 through 9 and then the letters A through F. Since there are not 16 different symbols for numbers in the common BASE10 counting system the letters A through F are needed to represent the numbers 10 through 15. Each place holder in the BASE16 counting system is 16 times greater than the one to the right. This is no different than decimal being 10 times greater and binary being 2 times greater. Each hexadecimal digit represents a binary nibble, or 4 bits. Figure 7 below is a conversion chart.

Figure 7: Base numbers conversion chart

Let's take the color BurntOrange for example. When you visit a color picker web site it will list this color as #CC6600. This hexadecimal number equates to the RGB values of (204, 102, 0). If you put those three decimal numbers into _RGB32 you will get the burnt orange color. Here's how the conversion looks.

|      204      |      102      |       0       |decimal RGB
|1 1 0 0|1 1 0 0|0 1 1 0|0 1 1 0|0 0 0 0|0 0 0 0|binary 24bits
|   C   |   C   |   6   |   6   |   0   |   0   |hexadecimal
      RED             GREEN            BLUE

Looking at the chart in Figure 7 we see that "C" in hex equates to 1100 in binary. Therefore CC converts to 11001100 and that binary number equals 204 in decimal. Likewise "6" in hex is 0110 in binary so 66 converts to 01100110 and that binary number equals 102 in decimal.

All programming languages understand and can use hexadecimal values. In fact, some languages such as Assembler rely heavily on their use. In order to tell QB64 that a number is being presented in hexadecimal form you must precede it with &H (an ampersand and then a capital H). For example, the hexadecimal number:

&HFF

is the same as presenting the number 255 in decimal or 11111111 in binary ( FF = 11111111 = 255 ). You can use these hexadecimal values along with the _RGB and _RGB32 statements as well:

BurntOrange~& = _RGB32(&HCC, &H66, &H00) ' the color burnt orange

The Alpha Channel and Transparency

The _LOADIMAGE statement supports PNG images that contain transparent layers. If the PNG image you are loading contains any transparency, or alpha channel, layer information it will be retained when loaded. You can create transparent PNG images by using any graphics program that supports alpha channel features. Transparent, or alpha channel, information is simply a predefined color, or colors, that has been marked as having transparency. This is done by setting the highest 8 bits to something other than 255 or 11111111. The following example program shows this difference. Two bee images are loaded and displayed on the screen. Both bee images have a bright magenta background (255, 0, 255) however the image file tbee0.PNG had the bright magenta color set to be fully transparent by my graphics program. Type the following program in to see the difference.

( This code can be found at .\tutorial\Lesson14\AlphaDemo.bas )

'** Demo - transparent and non-transparent image

DIM Sky& '            sky image
DIM Bee& '            bee image (no transparency)
DIM TransparentBee& ' bee image (with transparency)

Sky& = _LOADIMAGE(".\tutorial\Lesson14\sky.png", 32) '              load sky image
Bee& = _LOADIMAGE(".\tutorial\Lesson14\bee0.png", 32) '             load bee image
TransparentBee& = _LOADIMAGE(".\tutorial\Lesson14\tbee0.png", 32) ' load transparent bee image
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
SYSTEM '                                                            return to operating system

Figure 8: Standard image left, Transparent image right

The bee on the left, bee0.PNG, has no alpha channel changes to any of the colors so the bright magenta color is seen surrounding the image. The bee on the right, tbee0.PNG, has had the bright magenta alpha channel bits set to 0 making the color completely transparent and allowing the sky image behind to be seen. The bright magenta color is still there in the bee on the right but _PUTIMAGE uses the alpha channel information to allow whatever is underneath to come through.

The _SETALPHA Statement

QB64's _SETALPHA statement can be used to manipulate the alpha channel of any color, or range of colors, within an image to produce transparency. Using the same code let's make the first bee transparent as well by using the _SETALPHA statement.

( This code can be found at .\tutorial\Lesson14\SetAlphaDemo.bas )

'** Demo - transparent and non-transparent image

DIM Sky& '            sky image
DIM Bee& '            bee image (no transparency)
DIM TransparentBee& ' bee image (with transparency)

Sky& = _LOADIMAGE(".\tutorial\Lesson14\sky.png", 32) '              load sky image
Bee& = _LOADIMAGE(".\tutorial\Lesson14\bee0.png", 32) '             load bee image
_SETALPHA 0, _RGB32(255, 0, 255), Bee& '                            set bright magenta as transparent
TransparentBee& = _LOADIMAGE(".\tutorial\Lesson14\tbee0.png", 32) ' load transparent bee image
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
SYSTEM '                                                            return to operating system

Figure 9: Both bees now have transparency

The syntax for the _SETALPHA statement is:

_SETALPHA alpha%, color1~& TO color2~&, ImageHandle&

alpha% is the amount of transparency desired from 0 (fully transparent) to 255 (fully opaque or solid). color1~& is the starting color to apply the alpha channel changes to. color2~& is an optional second ending color allowing for a range of colors to have the alpha channel set. ImageHandle& is the image to have the alpha channel changes applied to.

In our example program above _SETALPHA was used to affect a single color using _RGB32. Remember that colors in QB64 have four components; red, green, blue, and alpha. When using _RGB32 the alpha channel defaults to 255 (fully opaque) so only the alpha channel of 255 was affected. To affect the entire range of magenta you would make this change:

_SETALPHA 0, _RGBA32(255, 0, 255, 0) TO _RGBA32(255, 0, 255, 255) ' all magenta transparent

Notice the use of _RGBA32 here which allows the addition of alpha channel values (_RGBA32 explained in more detail below). The following example highlights the difference between using _RGB32 and _RGBA32.

( This code can be found at .\tutorial\Lesson14\FadeDemo.bas )

'|
'| Tutorial - Lesson 14
'| Fade in a magenta box demo
'|

OPTION _EXPLICIT '       declare those variables!

DIM MagentaBox AS LONG ' a magenta box image
DIM c AS INTEGER '       generic counter

SCREEN _NEWIMAGE(640, 480, 32) '                                                 create graphics screen
MagentaBox = _NEWIMAGE(320, 240, 32) '                                           create box image
_DEST MagentaBox '                                                               draw on the box image
CLS , _RGB32(255, 0, 255) '                                                      color the box image magenta
_DEST 0 '                                                                        go back to the main screen

'+------------------------------------------------------------------------------------------+
'| The following block of code will NOT work. By using _RGB32 only the alpha channel of 255 |
'| is being affected by the _SETALPHA statement.                                            |
'+------------------------------------------------------------------------------------------+

c = 0 '                                                                          reset counter
DO '                                                                             begin fade loop
   CLS '                                                                         clear screen
   _LIMIT 30 '                                                                   slow down the fade
   LOCATE 2, 27: PRINT "Fading the magenta box in." '                            display header
   _SETALPHA c, _RGB32(255, 0, 255), MagentaBox '                                set the transparency level
   _PUTIMAGE (159, 119), MagentaBox '                                            place the box on the screen
   LOCATE 4, 28: PRINT "Current alpha level:"; c '                               display alpha level
   _DISPLAY '                                                                    update the screen with changes
    c = c + 1 '                                                                  increment counter
LOOP UNTIL c = 256 '                                                             leave when alpha fully opaque
_AUTODISPLAY '                                                                   give QB64 control of display
LOCATE 12, 28: PRINT "Ok, this is embarrasing." '                                print results
LOCATE 14, 28: PRINT "Let's try it with _RGBA and"
LOCATE 16, 28: PRINT "a range of alpha levels."
LOCATE 18, 28: PRINT "Press any key to continue.."
SLEEP '                                                                          wait for a key press

'+------------------------------------------------------------------------------------------+
'| The following block of code WILL work since the _RGBA command allows for the specific    |
'| alpha level to be included. Furthermore, supplying a range using the TO statement ensures|
'| all alpha levels are included.                                                           |
'+------------------------------------------------------------------------------------------+

c = 0 '                                                                          reset counter
DO '                                                                             begin fade loop
   CLS '                                                                         clear screen
   _LIMIT 30 '                                                                   slow down the fade
   LOCATE 2, 27: PRINT "Fading the magenta box in." '                            display header
   _SETALPHA c, _RGBA32(255, 0, 255, 0) TO _RGBA(255, 0, 255, 255), MagentaBox ' set the transparency level
   _PUTIMAGE (159, 119), MagentaBox '                                            place the box on the screen
   LOCATE 4, 28: PRINT "Current alpha level:"; c '                               display alpha level
   _DISPLAY '                                                                    update the screen with changes
    c = c + 1 '                                                                  increment counter
LOOP UNTIL c = 256 '                                                             leave when alpha fully opaque
LOCATE 25, 33: PRINT "That's better!" '                                          print results

The _SETALPHA statement is crazy powerful and takes some playing with to master. If all you need to do is set one color to transparent as we did in the example the _CLEARCOLOR statement is more often used.

The _CLEARCOLOR Statement

The _CLEARCOLOR statement is used to set a specific color to completely transparent. Once again let's modify the example code.

( This code can be found at .\tutorial\Lesson14\ClearColorDemo.bas )

'** Demo - transparent and non-transparent image

DIM Sky& '            sky image
DIM Bee& '            bee image (no transparency)
DIM TransparentBee& ' bee image (with transparency)

Sky& = _LOADIMAGE(".\tutorial\Lesson14\sky.png", 32) '              load sky image
Bee& = _LOADIMAGE(".\tutorial\Lesson14\bee0.png", 32) '             load bee image
_CLEARCOLOR _RGB32(255, 0, 255), Bee& '                             set bright magenta as transparent
TransparentBee& = _LOADIMAGE(".\tutorial\Lesson14\tbee0.png", 32) ' load transparent bee image
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
SYSTEM '                                                            return to operating system

This time instead of using the _SETALPHA statement in line 9 we replaced it with the _CLEARCOLOR statement. The code produces the same output screen as previously seen in Figure 9 above however this time the only supplied parameters needed are the color and image to set.

_CLEARCOLOR color~&, ImageHandle&

_CLEARCOLOR will always set the alpha channel value of color~& to 0 making it completely transparent. To remove the affects of using _CLEARCOLOR on an image the following is used:

_CLEARCOLOR _NONE, ImageHandle&

This effectively removes all transparency previously set with the _CLEARCOLOR statement. The _CLEARCOLOR statement can also be used a function to retrieve the current transparent color of an image.

Transparent~& = _CLEARCOLOR(ImageHandle&) ' get transparent color of image

The _RGBA32 and _RGBA Statements

The _RGBA32 and _RGBA statements operate the same as _RGB32 and _RGB with the addition of requiring the alpha transparency level at the same time. Instead of three parameters four will now be needed:

ColorValue~& = _RGBA32(red%, green%, blue%, alpha%)

The addition of an alpha% parameter allows the color to be created with a predetermined alpha level set.

Note: This statement is effectively the same as the _RGB32 variant that uses four color parameters. _RGB32 was originally not able to control alpha transparency values and didn't gain this ability until QB64 version 1.3. While redundant now, _RGBA32 and _RGBA will still be maintained for compatibility reasons.

The POINT Statement

The POINT statement can be used to retrieve the color of an image at any x,y coordinate location. Type the following example code in to see the POINT statement in action.

( This code can be found at .\tutorial\Lesson14\PointDemo.bas )

'** Demo - POINT

DIM Sky& '     sky image
DIM Bee& '     bee image (with transparency)
DIM Pcolor~& ' color at mouse pointer

Sky& = _LOADIMAGE(".\tutorial\Lesson14\sky.png", 32) '   load sky image
Bee& = _LOADIMAGE(".\tutorial\Lesson14\tbee0.png", 32) ' load transparent bee image
SCREEN _NEWIMAGE(640, 480, 32) '                         create graphics screen
DO '                                                     begin main loop
   _LIMIT 30 '                                           30 frames per second
   _PUTIMAGE (0, 0), Sky& '                              place sky image on screen
   _PUTIMAGE (250, 171), Bee& '                          place transparent bee image on screen
   WHILE _MOUSEINPUT: WEND '                             get latest mouse information
    Pcolor~& = POINT(_MOUSEX, _MOUSEY) '                 get color at mouse pointer
   LOCATE 2, 2 '                                         print results
   PRINT "Color:"; Pcolor~&
   LOCATE 3, 2
   PRINT "Red  :"; _RED32(Pcolor~&) '                    print just the red component value
   LOCATE 4, 2
   PRINT "Green:"; _GREEN32(Pcolor~&) '                  print just the green component values
   LOCATE 5, 2
   PRINT "Blue :"; _BLUE32(Pcolor~&) '                   print just the blue component values
   _DISPLAY '                                            update screen with changes
LOOP UNTIL _KEYDOWN(32) '                                end loop when ESC key pressed
SYSTEM '                                                 return to operating system

As you move the mouse pointer around on the screen the color at the mouse pointer is retrieved by the POINT statement in line 15 and saved to an unsigned long integer variable.  The POINT statement only needs to know the x,y coordinate of the image's color to retrieve it.

GetColor~& = POINT(x%, y%)

The _RED32, _GREEN32, and _BLUE32 Statements

In the previous example the red, green, and blue components of the color retrieved by POINT were also identified. The _RED32 statement will return a value from 0 to 255 indicating the amount of red in a color. The _GREEN32 statement will return a value between 0 and 255 indicating the amount of green in a color. Finally the _BLUE32 statement will return a value from 0 to 255 indicating the amount of blue in a color.

Red% = _RED32(color~&) '     return just the red component
Green% = _GREEN32(color~&) ' return just the green component
Blue% = _BLUE32(color~&) '   return just the blue component

There are also corresponding _RED, _GREEN, and _BLUE statements for working with non 32 bit color screens. These three statements will look up the palette values for the particular non 32 bit color screen and convert those values over to a 256 color index (as seen in ColorPick.BAS above). _RED, _GREEN, and _BLUE however will return the exact same values on 32 bit color screens as _RED32, _BLUE32, and _GREEN32 will.

As a rule when working in 32 bit color screens use _RED32, _GREEN32 and _BLUE32, and when working in non 32 bit color screens the use of _RED, _GREEN, and _BLUE is required. There is however no benefit to using the non 32 bit statements in a 32 bit color screen, in fact, they will be slower because of the extra overhead needed to do palette checks.

To summarize:

A Colorful Example - Hocus Pocus

It's time to put all this colorful code into action. The following program is saved in your .\tutorial\Lesson14\ subdirectory as HocusPocus.BAS and utilizes all of the statements up to this point. Copy HocusPocus.BAS to your qb64 folder and then load it into your IDE and execute it. Move the mouse around on the screen to make the magic happen.

( This code can be found at .\tutorial\Lesson14\HocusPocus.bas )

'*
'* Hocus Pocus V2.2 by Terry Ritchie 01/24/14
'* Updated 08/18/22 to reflect the new tutorial
'* Open source code - freely share and modify
'* Original author's name must remain intact
'*
'* 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(".\tutorial\Lesson14\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 component 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

Figure 10: Hocus Pocus!

This program is an example of particle effects (more on particle effects in lesson 18). The math involved with creating a program like this must be incredible right? Nope, there is no math involved other than resizing a _PUTIMAGE window, incrementing, and decrementing a few variables here and there. Even the gravity effect on the orbs is simulated with simple addition and subtraction.

Each of the colorful blooms created is actually an individual square image that contains an ever decreasing in size transparency layer using _PUTIMAGE's two coordinate placement system. This gives each square the illusion of fading out. The bloom itself is created using the CIRCLE statement contained in a loop to draw ever smaller circles on each square of slightly varying color that eventually leads to bright white. This gives them the appearance of glowing multicolored lights. THERE IS NO CODE KUNG FU going on here, just concepts from this lesson used to create an interesting effect.

Here at the beginning of the code some constants are set up to allow the look and the feel of the program to be changed easily. Lines 16 through 23 can all have their values changed to modify the size of the window, the bloom amounts, sizes, speeds, and to add a bounce to them if desired.

For fun set BOUNCE = TRUE and MAXLIFE = 128. The blooms will bounce a few times.

In lines 25 through 36 a TYPE definition is created that defines a single bloom. This TYPE definition is then used to create a dynamic array in line 38. Each time a new bloom is added to the screen the dynamic array is increased in size to hold it. We now have a construct that can hold many different objects at once that can all be manipulated easily by cycling through this array.

'--------------------------------
'- 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

Lines 50 through 62 prepare the program by creating the graphics screen, loading and starting the sound, hiding and then moving the mouse to the middle of the window. The first index in the array is told it has no object (line 61) and can be used to store the first object created.

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

SCREEN _NEWIMAGE(SWIDTH, SHEIGHT, 32) '      create 32 bit graphics screen
_SCREENMOVE _MIDDLE '                        move window to center of desktop
sa& = _SNDOPEN(".\tutorial\Lesson14\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 the mouse is being watched for any movement. If the variables x% or y% have changed, as indicated by their previous values oldx% and oldy%, then a FOR...NEXT loop is initiated in line 69 that counts to BLOOMAMOUNT. The subroutine HOCUS initiates a new bloom object at the current mouse position. Depending on the value of the constant BLOOMAMOUNT this may be done more than once.

The POCUS subroutine in line 76 scans through the array for any active blooms and updates their position and size. It then displays each active bloom to the screen.

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

At the beginning of the HOCUS subroutine the two variables hx% and hy% are defined as parameters to be passed into the HOCUS subroutine. These values are used as the x,y coordinate location to create a new bloom. The Abra() dynamic array is SHARED to allow access to its contents and finally local variables are declared that will be needed inside of the HOCUS subroutine.

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

Lines 114 through 140 are used for dynamic array maintenance. Lines 117 through 130 are used to find an available index number for the bloom to be created. Line 117 sets up a loop that will loop as many times as there are indexes in the array. If the variable Abra(Count%).lifespan has reached 0 in line 118 that means the bloom contained at this index has run its course. Lines 119 through 122 mark this index as empty and removes the dead bloom image from memory. Lines 123 through 125 check to see if a free index has already been chosen and if not this newly available index is chosen. In lines 126 through 128 a variable named CleanUp% used as a flag is set to FALSE if any index still has an active bloom contained in it.

Lines 131 through 134 resets the dynamic array since there are no active blooms contained anywhere in the array as indicated by CleanUp% being TRUE. This assures that size of the array does not get too large. When the user stops moving the mouse around and all color blooms have run their course the array is reset freeing the used memory. If however there are active blooms in the array lines 136 through 140 check to see if one of the indexes in the array were free. If line 136 sees that Index% is still holding the value of 0 then a free index was not found and line 137 needs to increase the dynamic array in size by 1. That newly created index value is now going to be used for the new bloom to be created.

   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

Lines 141 through 157 sets up the dynamic array variables with values needed to create a bloom. Line 149 creates the image surface that will hold the bloom. When new images are created by default their color is (0, 0, 0, 0) meaning that they are transparent black. Many times, especially after _NEWIMAGE is used to create a SCREEN the CLS command is used to remove the transparent layer effectively changing the image's color to (255, 0, 0, 0). This will not be done here as we'll use that default transparency to our advantage later on.

Lines 150 through 152 creates the three random color components of the bloom color. Lines 153 through 155 set a color step value that will be used to change the blooms color as it gets smaller. Finally in line 157 an alpha transparency step value is also set used to make the bloom less transparent as it gets smaller.

    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

Lines 158 through 179 is where the actual bloom gets created. First the current destination must be saved in line 158 since we are going to change the destination to the newly created image in line 159. In line 160 a counter is set up with the same size as the bloom. A loop is then set up in line 161 that will continue to loop until the value of Count% becomes 0. Count% is the radius value of each circle to be drawn within the bloom image.

Line 165 draws a circle at the center of the image using the radius value contained in Count%. The color used is defined by the current Red%, Green%, and Blue% component color variables.

Line 170 paints the circle starting at the center of the image using the same color as the previously drawn circle.

Line 172 then sets the alpha transparency of that color based on the value contained in Alpha%.

Lines 173 through 175 then increase the red, green, and blue color components in effect getting ever closer to white as the loop progresses.

Line 176 increases the Alpha% value making each subsequent circle to be drawn less transparent. As the bloom's rings created by the CIRCLE statement get ever smaller they become less transparent until the final circle in white in the center is completely opaque. A glowing orb is formed!

Line 177 decreases the size of the next circle and the loop starts again until finally Count% has reached a radius of 0.

When the loop is complete the original destination is restored in line 179.

   _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

Figure 11 below shows the progression of the bloom as it is formed.

Figure 11: The creation of a glowing orb

The POCUS subroutine is used to update and display all active blooms within the Abra() dynamic array. Line 197 retrieves the largest index value contained within the array. A loop is then set up which will continue looping until the value of c% becomes 0.

Line 199 checks to see if the bloom is still active. If it is line 200 gets the current size of the bloom.

Line 201 then uses this size to go from a center point outward to that size using _PUTIMAGE. This creates a square region to squeeze the bloom image into. As the lifespan of the bloom progresses this size gets smaller squeezing the image into a smaller and smaller square. This effectively makes the bloom appear to be fading out as time goes on.

In lines 202 through 205 the lifespan of the bloom is decreased, the size of the bloom is decreased, and the x and y location of the bloom is updated.

In lines 206 through 212 then bloom is checked to see if it left the bottom of the screen. If it has and BOUNCE is TRUE then the vertical velocity is simply reversed to give the bloom a bouncing effect. If BOUNCE is set to FALSE then the bloom is simply killed off by setting its .lifespan variable to 0 as it is no longer needed.

Lines 213 and 214 update the x and y speed vectors of the bloom. The horizontal (x) speed is reduced by 10% and the vertical (y) speed is increased which simulates a gravity effect on the bloom.

Line 216 moves onto the next array index to update the bloom located there.

As you can see the effect is rather simple to achieve: create a square image, draw circles with varying color and transparency onto that image, squeeze that square into an ever increasingly smaller area for a set length of time. Rinse and repeat. No difficult math, no code Kung Fu.

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

Your Turn

ToDo - Any ideas?

Commands and Concepts Learned