Task 15: Working With Images  

Contents

The _NEWIMAGE Statement
The _LOADIMAGE Statement
The _WIDTH and _HEIGHT Statements
The _PUTIMAGE Statement
The _DEST and _SOURCE Statements
Resizing an Image With _PUTIMAGE
Flipping an Image With _PUTIMAGE
Copy and Paste With _PUTIMAGE
The _COPYIMAGE Statement
The _SCREENIMAGE Statement
The _FREEIMAGE Statement
Your Turn - Chess Board
Command Reference



Task 7 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 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 tasks _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 that _NEWIMAGE created as the default window. This image becomes the destination for any screen related statements to use.

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 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 loaded and mode is 32 for 32bit color images. There is another mode, 33 for hardware images, that we'll discuss in another task.

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.



Remember, a 640x480 image actually contains the coordinates 0 - 639 (x) and 0 through 479 (y) because computers start with zero.


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 once 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 onto 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 a 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 which is 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's _NEWIMAGE 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.

SUB MySubroutine()

DIM OriginalSource&, OriginalDest& ' save source/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

END SUB



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, and 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 on the destination image
dy1%    - the first corner y coordinate on the destination image
dx2%    - the second corner x coordinate on the destination image
dy2%    - the second corner y coordinate on the destination image
Source& - the source image handle to be placed on the destination image
Dest&   - the destination image handle where the source image is to be placed
sx1%    - the first corner x coordinate on the source image
sy1%    - the first corner y coordinate on the source image
sx2%    - the second corner x coordinate on the source image
sy2%    - the second corner y coordinate on the source image

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\task15\ 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 task.

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.





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.


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, 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 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.



The _COPYIMAGE Statement


The _COPYIMAGE statement is used to create a copy of any image in memory including 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. 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 and that 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

Or, 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. 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.

You should also free any assets from subroutines and functions that create them for local use. 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.



Your Turn


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\task15\ directory. Figure 15 below describes what is contained in the file.


Figure 15 - Description of the chess.png File the Art Department Sent

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.
Having never worked with creating random numbers you do some research and conclude the following:

RandonNumber% = INT(RND(1) * SomeNumber%) + 1

The formula above will produce a random integer value between 1 and the supplied SomeNumber%. For instance, the following line of code will produce a random number between 1 and 10:

RandomNumber% = INT(RND(1) * 10) + 1 ' random number from 1 to 10

Simply supplying the value of 10 for SomeNumber% produced this result. You have also noticed that if you leave the +1 off the end of the formula that the random numbers will start with 0 and end with SomeNumber%-1. For example, the same line above without the +1 will yield a random number between 0 and 9:

RandomNumber% = INT(RND(1) * 10) ' random number from 0 to 9

Also during your research into random numbers you concluded that the following line of code:

RANDOMIZE TIMER' seed the random number generator

must be included somewhere within your code to make truly random numbers.

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

Click here to see the solution.




Command Reference


New commands introduced in this task:

_NEWIMAGE
_LOADIMAGE
_WIDTH
_HEIGHT
_PUTIMAGE
_DEST
_SOURCE
_FREEIMAGE
_COPYIMAGE
_SCREENIMAGE


New concepts introduced in this task:

BMP
JPG (JPEG)
PNG
GIF
PSD
PIC
PNM
TGA
Lossless Compression
Color Depth
Sprite
Sprite Sheet




Commands learned in Task 14:

BEEP
SOUND
PLAY
_SNDOPEN
_SNDPLAY
_SNDPLAYCOPY
_SNDLOOP
_SNDPLAYFILE
_SNDPLAYING
_SNDSTOP
_SNDVOL
_SNDPAUSE
_SNDPAUSED
_SNDLIMIT
_SNDLEN
_SNDSETPOS
_SNDGETPOS
_SNDCLOSE


Concepts learned in Task 14:

FM Synthesis
MIDI
WAV
OGG
MP3




Commands learned in Task 13:

_DIREXISTS
_FILEEXISTS
CHDIR
MKDIR
RMDIR
KILL
NAME...AS
OPEN
INPUT (file statement)
OUTPUT
APPEND
BINARY
RANDOM
PRINT (file statement)
WRITE (file statement)
LINE INPUT (file statement)
FREEFILE
LOF


Concepts learned in Task 13:

Sequential File
Comma Separated Value File (CSV)
Records
Database




Commands learned in Task 12:

AND
OR
XOR
NOT
\ (Integer Division)
MOD
INT()
CINT()
CLNG()
CSNG()
CDBL()
_ROUND()
FIX()
ABS()
SGN()
SIN()
COS()
TAN()
ATN()
READ
DATA


Concepts learned in Task 12:

numbering system
transistor
binary
binary numbering system
George Boole
Boolean logic
logic gate
bitwise math
flags
Banker's Rounding
sine
radian
cosine
tangent
arctangent



Commands learned in Task 11:

UCASE$()
LCASE$()
LTRIM$()
RTRIM$()
_TRIM$()
INSTR()
STR$()
DATE$
LEFT$()
RIGHT$()
MID$()
ASC()
STRING$()
SPACE$()
SWAP


Concepts learned in Task 11:

parse



Commands learned in Task 10:

DIM
TYPE
END TYPE
AS
REDIM
_PRESERVE
UBOUND
LBOUND
OPTION BASE


Concepts learned in Task 10:

array
one dimensional array
element
index
two dimensional array
three dimensional array
Dynamic Array
Static Array




Commands learned in Task 9:

LINE INPUT
INKEY$
CHR$()
_KEYDOWN
_KEYHIT
_MOUSEX
_MOUSEY
_MOUSEINPUT
_MOUSEHIDE
_MOUSESHOW
_MOUSEMOVE
_MOUSEBUTTON


Concepts learned in Task 9:

BIOS
ASCII
buffer
CMOS
ROM
ASCII chart



Commands learned in Task 8:

SUB
END SUB
FUNCTION
END FUNCTION
SHARED

Concepts learned in Task 8:

subroutine
function
local variables
global variables
Pythagorean Theorem



Commands learned in Task 7:

SCREEN
LINE
CIRCLE
PAINT
PSET
_RGB32()


Concepts learned in Task 7:

pixels
radian
counter-clockwise
aspect ratio


Commands learned in Task 6:

FOR...NEXT
STEP
CLS
SLEEP
_DELAY
SYSTEM
DO...LOOP (and variations)
WHILE...WEND

Concepts learned in Task 6:

frame rate
controlled loops
conditional loops


Commands learned in Task 5:

TIME$
VAL()
IF...THEN
AND
END
GOTO
SELECT CASE...END SELECT
CASE
TO
IS
CASE ELSE
colon ( : )

Concepts learned in Task 5:

relational operators
functions
nesting
order of operations
Boolean
conditions
indenting
loops
labels


Commands learned in Task 4:

INPUT
DIM
REM or '
CONST

Concepts learned in Task 4:

variables
type declarations
literal strings
concatenation
integers
long integers
single precision
double precision
strings
null strings
reserved words
operators ( +, -, *, / )
declare
constant


Commands learned in Task 3:

PRINT

Concepts learned in Task 3:

execute
statement
expression
literal string
syntax error