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:
Use the _RED32, _GREEN32, and _BLUE32 32 bit statements in 32 bit color screens (SCREEN _NEWIMAGE(640, 480, 32), etc..)
use the _RED, _GREEN, and _BLUE statements in non 32 bit color screens (SCREEN 0-13, SCREEN _NEWIMAGE(640, 480, 256), etc..)
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
New commands introduced in this lesson:
_RGB32
_RGB
&H
_SETALPHA
_CLEARCOLOR
_RGBA32
_RGBA
POINT
_RED32
_GREEN32
_BLUE32
_RED
_GREEN
_BLUE
New concepts introduced in this lesson:
oscilloscope
persistence of vision
color depth
cathode ray tube (CRT)
monochrome
color graphics adapter (CGA)
RGB
enhanced graphics adapter (EGA)
video graphics array (VGA)
SVGA
XGA
alpha transparency
HTML
hexadecimal
assembler