Lesson 13: Working With Images
Lesson 5 introduced you to the BASIC graphics commands that programmers have had available since the 1980s. While these commands are perfect for creating primitive shapes and colors you are not going to create anything close to photo realistic images with them. QB64 offers a powerful command set for working with and manipulating images.
The _NEWIMAGE Statement
The _NEWIMAGE statement creates an image surface in memory and returns a long integer numeric value as a handle pointing to the image. _NEWIMAGE requires three parameters:
handle& = _NEWIMAGE(width&, height&, mode%)
width& is the width in pixels of the new image surface.
height& is the height in pixels of the new image surface.
mode% is the type of surface image to create and can be the following:
0 - a text screen (supply width& and height& as characters, not pixels)
1, 2, 7, 8, 9, 10, 11, 12, 13 - QuickBasic legacy screens
256 - 256 color graphics image surface (8bit)
32 - 16.7 million color graphics image surface (32bit)
This course will focus on 32bit color image surfaces only. The other modes are still useful however especially when converting legacy software to operate on today's graphics hardware.
In all of the examples in previous lessons _NEWIMAGE has been used along with the SCREEN statement to create a new graphics window. The SCREEN statement is being instructed to use the image in memory as the default window that _NEWIMAGE created. This image becomes the destination for any screen related statements used.
However, it's possible to use any image created by _NEWIMAGE as the default output screen. The following example illustrates how four images can created and used as the output screen at any time. Save this example as MultipleScreens.BAS when completed.
Line 5 in the example creates an array of long integers to hold the handle value for four surface images. Lines 10 through 17 of the example creates the four surface images in memory and then adds some identifying text and graphics to each image. Line 22 through 30 of the example cycles through the four surface images and makes each one the default output window using the SCREEN statement.
Think on an image surface created in memory with _NEWIMAGE as just another screen surface you can manipulate with graphics and text statements but happens to be hidden from view. It does not matter if the image surface is currently the default screen or not as seen in lines 10 through 17. By making an image surface the destination (more on destination later) it becomes the default image surface for text and graphics statements to work with. This for instance allows you to update images in the background while the user is viewing a completely different image in the default window. Image related statements in QB64 are very powerful.
The _LOADIMAGE Statement
The _LOADIMAGE statement loads an image surface into memory from a specified image file and returns a long integer numeric value as a handle pointing to the image. _LOADIMAGE is capable of loading PNG, BMP, JPG (JPEG), PSD, PIC, PNM, TGA, and GIF graphic file formats.
handle& = _LOADIMAGE(Filename$, mode%)
Filename$ is the name of the image file you want to load and mode is 32 for 32bit color images. There is another mode, 33 for hardware images, that we'll discuss in another lesson.
Type in the following example program that loads an image from a file and then uses that image as the default window screen. Save the code as LoadimageDemo.BAS when finished.
Figure 1: The sky image loaded by _LOADIMAGE
The only limit on the number, size, and dimensions of the images you load in is the amount of RAM a computer has.
PNG, and JPG (JPEG), both very popular image file types, support 32 bit color and excellent compression for smaller file sizes. They both also support transparency, or a color that can be designated as invisible to allow other graphics to show through (more on transparency later). However, QB64 only supports transparency with PNG files and not JPG. Because of JPG's compression algorithm many JPGs are "blocky" in appearance which can lead to poor image quality. PNG files on the other hand offer a compression algorithm almost as good as JPG without losing image quality, known as lossless compression.
My first and usually only choice in graphics files is PNG because of their small file size, transparency capabilities and high image quality. Many paint and photo programs out today support loading, converting and saving a myriad of image file types. As a game programmer you will need to have a paint program that you are very familiar with that supports a transparency, or alpha channel, layer.
The _WIDTH and _HEIGHT Statements
Many times you will need to know the dimensions of the images you load into memory. The _WIDTH statement returns the width in pixels of an image and the _HEIGHT statement returns the height in pixels of an image. The following code uses these two statements to create a SCREEN that matches the width and height of a loaded image.
The _PUTIMAGE Statement
Now that you know how to create new images and load images from a file it's time to learn how to place them on the screen. It's all good that images can be used as the main window but the real power of images is being able to manipulate them. The _PUTIMAGE statement is going to be your main vehicle for placing and moving images around on the screen. It's an extremely powerful and versatile command and arguably one of the most difficult to comprehend at first.
The most basic use of _PUTIMAGE is to place an image loaded in memory onto the default destination image which is most commonly the main image window.
_PUTIMAGE (x%, y%), ImageHandle&
x% and y% are the coordinates on the destination image, or screen, you wish to place the image. The following example loads an image into memory and places it on the main window surface. Save the code as PutimageDemo1.BAS.
Figure 2: An image file placed on the screen
Lines 8 through 12 in the example code loads the image and prepares the window surface. Lines 13 and 14 use the _WIDTH and _HEIGHT statements to help calculate the center x,y coordinate for the image loaded in memory. Images, just like the screen, have coordinate 0,0 in the upper left hand corner. If the code would have placed the image at the exact center of the screen 319, 239 the image would not be centered. The upper left hand corner of the image would be there instead. The code needs to shift the image half of its width to the left and half of its height up to truly center the image.
cx% = (640 - _WIDTH(Bee&)) \ 2 ' calculate x center position
This is a very common method for centering an image onto another image. You simply take the width of the main image, in this case the screen width of 640 and subtract the width of the image being placed onto it which is _WIDTH(Bee&). Integer dividing by 2 gives the left offset needed to center the image horizontally. Likewise:
cy% = (480 = _HEIGHT(Bee&)) \ 2 ' calculate y center position
is achieving the same thing with the vertical center point by using the height of the screen and image instead. Once the code has determined the x,y coordinate to place the image then _PUTIMAGE is used.
_PUTIMAGE (cx%, cy%), Bee& ' place image onto center of screen
Again, this is the most basic form of _PUTIMAGE available and will be used most of the time when moving objects around on the screen.
The _DEST and _SOURCE Statements
Before exploring more of the options available with _PUTIMAGE we need to understand source and destination images first. A destination image is the image that graphics related commands will direct their output to. Think of this as the active image. A source image is the image that is being used in some way to affect the destination image. In the example code given with _PUTIMAGE above this line of code:
_PUTIMAGE (cx%, cy%), Bee& ' place image onto the center of the screen
designates the Bee& image as the source and is going to be used to change the SCREEN which is the default destination image. Since no destination was explicitly set in the example code the screen becomes the default destination. A SCREEN statement that creates a window by using _NEWIMAGE will always be the image destination value of 0. The _PUTIMAGE statement also supports supplying the destination image like so:
_PUTIMAGE (cx%, cy%), Bee&, 0 ' place bee image onto image 0 (the screen)
The above line of code would achieve the exact same effect. The next example code will help to illustrate this better. Save the code as PutimageDemo2.BAS.
Figure 3: An image on an image on an image
The _DEST statement in line 11 is used to designate the square image created in line 10 as the destination image. Any graphics related command from this point on with see the Square& image as the image to manipulate. The CLS statement in line 12 will therefore clear the Square& image with a bright magenta color.
Line 13 creates a new window surface using _NEWIMAGE. This newly created window surface now becomes the destination image. Line 19 of the example code:
_PUTIMAGE (cx%, cy%), Bee&, Square& ' place bee image onto center of square image
is bypassing the surface image entirely by assigning Bee& as the source image and Square& as the destination image. The Bee& image will get placed onto the Square& image in the background. In line 22:
_PUTIMAGE (cx%, cy%), Square& ' place square image centered on screen
the source image, Square&, is placed onto the window surface since no destination was designated. Line 13 designated the window surface as the new default destination so the Square& image gets placed there.
The _SOURCE statement is used to designate an image as the source image. The following example demonstrates how _SOURCE and _DEST can be used to identify images before the _PUTIMAGE statement is called. Save this code as SourceDestDemo.BAS.
The exact same output as seen in Figure 3 above is achieved however this time in lines 21 and 26 of the code _PUTIMAGE was not supplied with a source or destination image to work with.
By predetermining the source image using _SOURCE and the destination image using _DEST, as in done in lines 19 and 20 and again in lines 24 and 25, _PUTIMAGE will use these designations as the default images.
Personally I try to avoid _PUTIMAGE statements without at least a source image identified. This line by itself:
_PUTIMAGE (cx%, cy%)
does not tell me much about what is going on. I am forced to go back through the code to identify the source and destination images myself. However, there will be times when identifying both beforehand may be needed by the code being created.
_SOURCE and _DEST can also be used to identify the current source and destination images.
CurrentSource& = _SOURCE ' return the current source image handle
CurrentDestination& = _DEST ' return the current destination handle
This is handy for subroutines and functions that make changes to different graphic images. At the beginning of a subroutine or function the source and destination images can be saved and then restored before leaving the subroutine or function.
DIM OriginalSource&, OriginalDest& ' save current source and dest here
OriginalSource& = _SOURCE ' save the current source image handle
OriginalDest& = _DEST ' save the current dest image handle
... ' code here
_SOURCE = OriginalSource& ' restore the source image
_DEST = OriginalDest& ' restore the destination image
Resizing an Image With _PUTIMAGE
The _PUTIMAGE statement can accept two coordinate pairs to define an area to place a source image onto a destination image.
_PUTIMAGE (dx1%, dy1%) - (dx2%, dy2%), SourceHandle&, DestHandle&
The (dx1%, dy1%) coordinate pair defines the upper left coordinate of the source image and the (dx2%, dy2%) coordinate pair defines the lower right hand corner. Type in the following example code to see resizing in action. Save the code as PutimageDemo3.BAS.
Figure 4: That's a big bee!
Lines 10 through 13 define a box the same size as the Bee& image. Lines 22 through 32 then increase or decrease the box coordinates depending on if the up or down arrow key is pressed. Line 21:
_PUTIMAGE (dx1%, dy1%)-(dx2%, dy2%), Bee&
then stretches or squeezes the Bee& image to fix the box contained within the coordinates. In this example the aspect ratio of the image remains intact because the coordinate pairs are resized together. However, if you want, you can resize each dimension independently as the next example illustrates. Save this code as PutimageDEMO4.BAS.
Figure 5: Who stepped on the bee?
Flipping an Image With _PUTIMAGE
Playing around with the previous two examples you probably noticed something ... the bee image would flip horizontally and vertically when resizing the image. This is because if the values of either x,y pair crossed each other's path the box will essentially flip. That is, vertically the top left becomes the bottom left and the bottom right becomes the top right. Horizontally the top left becomes the top right and the bottom right becomes the bottom left.
The following code flips the bee image vertically as the bee travels up and down the screen. Save this example as PutimageDemo5.BAS.
Figure 6: The bee now has a sense of direction
_PUTIMAGE always draws images according to the direction of the coordinate pairs given to it. If the bee is heading upward the coordinates given to _PUTIMAGE match the image's original orientation, that is the first coordinate pair is located at the top left as compared to the second coordinate pair.
'** draw from top left to lower right
_PUTIMAGE (BeeX% - Adder%, BeeY% - Adder%)-(BeeX% + Adder%, BeeY% + Adder%), Bee&(WhichBee%)
However, when the bee is traveling in a downward direction the coordinate pairs are reversed which in effect flips the bee image vertically.
'** draw from lower right to top left
_PUTIMAGE (BeeX% + Adder%, BeeY% + Adder%)-(BeeX% - Adder%, BeeY% - Adder%), Bee&(WhichBee%)
Figure 7 below shows a visual representation of this.
Figure 7: Flipping the bee
Therefore it's possible to flip an image horizontally, vertically, or both at the same time by manipulating the starting and ending coordinate pair values. The next example shows _PUTIMAGE doing just that. Use your mouse to move the bee around and manipulate the image in the center of the screen. Save this example code as PutimageDemo6.BAS.
Figure 8: Flipping an image horizontally, vertically, or both at the same time
The center Arrows& image will flip depending on which of the four quadrants the bee is in. As you can see it's possible to create three mirror images of an original image using this method. This comes in very handy when working with game graphics. Why draw Mario images running right and left when you can just create images for one direction then flip them for the other or perhaps a space ship that need to go in all four directions. Use one spaceship image and then mirror the other three directions as needed.
Copy and Paste With _PUTIMAGE
The full command syntax for _PUTIMAGE is:
_PUTIMAGE (dx1%, dy1%)-(dx2%, dy2%), Source&, Dest&, (sx1%, sy1%)-(sx2%, sy2%)
dx1% - the first corner x coordinate within the destination image
dy1% - the first corner y coordinate within the destination image
dx2% - the second corner x coordinate within the destination image
dy2% - the second corner y coordinate within the destination image
Source& - the source image handle to be placed within the destination image
Dest& - the destination image handle where the source image is to be placed
sx1% - the first corner x coordinate within the source image
sy1% - the first corner y coordinate within the source image
sx2% - the second corner x coordinate within the source image
sy2% - the second corner y coordinate within the source image
When I first encountered _PUTIMAGE I was a bit intimidated by the construct of this statement. However with a little bit of playing and familiarity this statement soon became my favorite. The command syntax you see above allows for copying a portion of one image and pasting to another while at the same time flipping and resizing the copied image. Very, very powerful.
Included in the .\tutorial\Lesson13\ directory is a graphics file called flags.PNG. There are eight separate images of a waving flag contained in it as seen in Figure 9 below.
Figure 9: Eight flag images
Using _PUTIMAGE it is possible to copy each individual flag image and paste that image to another image. The image in Figure 9 is referred to as a sprite sheet and we'll discuss them in more detail in a later lesson.
Type in the following example code that copies the first two flag images and pastes them to the current window surface. Press a key to advance to the next flag image. Save the code as GetFlag.BAS when finished typing it in.
Figure 10: The first _PUTIMAGE statement at work
The first _PUTIMAGE statement as described in Figure 10 above has been instructed to copy a portion of the AllFlags& image and place that portion of the source image onto the screen designated as _DEST. Since a destination image was not specifically created using _DEST the destination image becomes the default screen.
Figure 11: The second _PUTIMAGE statement at work
The second _PUTIMAGE statement as see in Figure 11 above has once again been instructed to copy a portion of the source image AllFlags&. Notice that the first coordinate pair values have not changed since the destination, the screen in this case, has not changed. The second source coordinate pair however has changed to reflect the location of the second flag image on the overall AllFlags& image.
In the following example all individual flag images are copied from the master image and placed into an array. The flag images contained in the array are then displayed one after another to create an animation. Save the following code as WavingFlag.BAS when finished.
Figure 12: All rise
Using _PUTIMAGE to pull a series of images from a master image and then store them in memory for later use is very common in game programming. It's an efficient way to store many images within one image file. Instead of keeping track of eight individual flag images that need to be loaded one file can be loaded and then the flag images can be parsed out using _PUTIMAGE. Many times in games you'll see company logos being displayed before the game starts. Usually what is happening in the background is the images and sounds (the assets) are being loaded into memory for later use (DOOM WAD files for instance). Displaying cut scenes is another way to distract the player while the next level's assets are being loaded.
The _PUTIMAGE statement takes some time to master and I'm sure the _PUTIMAGE QB64 Wiki page will be your best friend until you do.
Note: Since I live in the United States and am a former Marine I'm obviously biased to create the American flag and have the American national anthem playing in the background. I welcome everyone from all over this big blue marble of ours to use this tutorial regardless of your country's political standing in the world. Find (or create) some waving flag images of your country's flag and create a sprite sheet to use in this code instead. Have your national anthem play in the background as well. If you do this please post your source code and image and sound assets to the QB64 forum so we can all see your flag waving and listen to your anthem. Hopefully some day we wave one united flag for mankind as we venture beyond our Earthly boundaries.
The _COPYIMAGE Statement
The _COPYIMAGE statement is used to create a copy of any image in memory which includes the entire screen. Type in the following example program to see how _COPYIMAGE is used. Save the code as CloneTrooper.BAS when completed.
Figure 13: Cloned Storm Trooper
LOL, that's one way to remove a clone I guess! Yes, most of this example was for fun. I love how easy it is to use QB64 to create something silly like this.
The line of code relating to the _COPYIMAGE statement is 54:
Clone& = _COPYIMAGE(Standing&) ' create a copy of the standing image
_COPYIMAGE can copy any valid image which also includes the screen. This is handy if you need a quick copy of the screen before an action is taken. This way the user can select "undo" in your program and you can simply place this copy back onto the screen.
The _SCREENIMAGE Statement (Windows only)
The _SCREENIMAGE statement is used to take a snapshot of the computer's desktop. _SCREENIMAGE can be used with no parameters to grab the entire desktop.
Desktop& = _SCREENIMAGE ' grab an image of the desktop
By supplying coordinates a portion of the desktop can be grabbed.
Desktop& = _SCREENIMAGE(0, 0, 639, 479) ' upper left 640x480 area of desktop
Here's an example that uses _SCREENIMAGE to play a prank on your friends. Use your mouse to click around on the screen. Pressing the ESC key will end the program. Save this example as ScreenImagePrank.BAS.
When the unsuspecting user of your prank clicks on the "desktop" a bullet hole will appear with a gun shot heard. Eventually they will press the escape key and the program will terminate back to the original desktop leaving the user baffled. I put a program similar to this on my wife's computer and had Task Scheduler run it once a day. Her version had a timer that would end the program after one minute so by the time she came and got me to show what was happening everything was fine. Funny stuff ... until she found out!
The _FREEIMAGE Statement
I'm sure you have noticed by now that every example has used the _FREEIMAGE statement. The _FREEIMAGE statement is used to remove images from memory. You should get into the habit of removing all assets (images, sounds, fonts, etc..) from the computer before your program terminates. While all operating systems today have a file manager that does a good job of cleaning up after program termination and most languages have "garbage collection" as well, you should not rely on these to do your dirty work. It's lazy for one and could result in making your user's computer unstable.
Note: You should also free any assets from subroutines and functions that create them for local use before you exit the subroutine or function. Not doing so will result in the asset being created again and again which will eventually fill your user's memory up causing either a program or operating system crash.
You were just hired as a contract graphics programmer by a rather large gaming company. The company is currently working on a mobile chess platform and want to do something about the chess board supplied by their art department. Figure 14 below is the chess board graphic that was supplied by the art department but rejected by the team leader.
Figure 14: The chess board created by the art department
The team leader feels that the board looks too repetitive and the tiles need some randomness to them. The art department sent you a graphic file called "chess.png" located in your .\tutorial\Lesson13\ directory. Figure 15 below describes what is contained in the file.
Figure 15: Description of the chess.png file the art department sent you
The team leader has sent you the following requirements to accomplish:
When a subroutine called "MakeChessBoard" is called a random chess board is created and stored in a long integer variable named ChessBoard& that can be used anywhere throughout the program's code.
The white and black tiles must be randomly rotated on the board. Tiles should be randomly flipped horizontally, vertically, both, or not at all. This ensures that every time "MakeChessBoard" is called a new random chess board will be created.
Save the finished code as MakeChessBoard.BAS.
The team leader has given you a three day deadline to complete this assignment.
Some things to consider:
The very first time "MakeChessBoard" is called there will be no image contained in ChessBoard&. The ChessBoard& image only needs to be made the very first time the "MakeChessBoard" subroutine is called.
The ChessBoard& variable must somehow be global throughout the entire source code.
Your program should include a small proof of concept code in the main program section. Your team leader will be able to press a key to see the chess board being created randomly over and over again when "MakeChessBoard" is called until he/she is satisfied the code is working properly. Figure 16 below is an example of the proof of concept.
Figure 16: The program as submitted for approval