In this section, you will learn how to master the image conversion ad manipulation. ugBASIC is equipped with a sophisticated image conversion system, which allows the use of modern formats (like JPG or PNG) which are then “translated” into a native format of the target machine. This mechanism is exposed by means of a series of primitives, which allow you to load images, to draw them on the screen and to move them.
This section will limit itself to giving general indications on the functioning of these primitives. It must in fact be taken into account that ugBASIC is an isomorphic language, therefore it does not provide a real abstraction but rather exposes the mechanisms of the specific target.
For this reason, it is necessary to carefully study the “form” of the graphic data, so that they can be used easily by all targets.
In order to draw an image, it is necessary to define it and, above all, to allocate space to keep the graphic and color information. This is done with the use of the BUFFER
data type. This type of data is special: in fact, it represents a certain memory area, of a certain size and starting from a certain position in the memory.
Defining a BUFFER
is simple, because it is sufficient to use the following syntax:
b = #[aa55aa55aa55aa55]
The sequence of hexadecimal digits in square brackets describes the contents of the buffer b
. In this specific case, we are defining a memory area of 8 bytes, with an alternating pattern of 55
(85 decimal) and aa
(170 decimal) values.
For the purpose of image manipulation, a specific type has been introduced: IMAGE
. This type of data is equivalent to a BUFFER
but the content is declined according to the target and the specific graphics mode. In other words, it is possible to define an image as a buffer is defined, simply by asking ugBASIC to consider it a variable of type IMAGE
, and putting a content that is compatible with the target / chipset:
image = (IMAGE) #[1004aa55aa55aa55aa55]
Although the actual content may vary, the format assumes that the first two bytes represent the width (in pixels) and height (in pixels) (currently: 16×4 pixel), while the rest of the sequence represents the graphic content.
Recall that ugBASIC is an isomorphic language, it does not code the content homogeneously. Each target has its own format, and even each individual graphics mode. It follows that it is neither easy nor advisable to manually define such information but other primitives, much more powerful and effective, must be used.
To load an image from the development computer, you need to use the LOAD IMAGE
function. This function accepts, as a parameter, the name of a file located on the development system and returns a value of type IMAGE
, which can then be used.
airplaneImage = LOAD IMAGE("examples/air_attack_airplane.png")
Note that the term “load” does not represent the action actually performed by the compiler. The compiler, in fact, will take the file, convert it into a format suitable for the boundary conditions, and store it in the data segment of the program. As a result, there is no type of access to a disk, tape or other media. Furthermore, there are obvious limitations in the size of a single image or set of images, which cannot be overcome.
Loading the same image several times results in the generation of a single image buffer, without repetition.
REM airplaneImage1 and airplaneImage2 are the very same buffer! airplaneImage1 = LOAD IMAGE("examples/air_attack_airplane.png") airplaneImage2 = LOAD IMAGE("examples/air_attack_airplane.png")
To avoid this behavior, the AS
keyword must be used.
REM airplaneImage1 and airplaneImage2 are the very same buffer! airplaneImage1 = LOAD IMAGE("examples/air_attack_airplane.png" AS "airplane1") airplaneImage2 = LOAD IMAGE("examples/air_attack_airplane.png" AS "airplane2")
ugBASIC offers a primitive for drawing images, and it's called PUT IMAGE.
PUT IMAGE airplane AT 16,16
This primitive is designed so that it can exploit, in the most efficient way possible, the graphics subsystem. For this reason, although it is possible to indicate an absolute position from which to start drawing, there is no guarantee that this position will be respected.
For example, on many systems there is an alignment to the nearest 8 horizontal pixels (byte alignment), while on others this alignment may also be present vertically. In general, positioning an image at (0,0) is guaranteed to cause the image to be drawn starting from the upper left corner.
We began by arguing that ugBASIC converts images according to contextual constraints. This is implemented through the evaluation of both which target is being compiled and the graphics mode in use at the moment. Both of these factors are evaluated at the time of compilation, and reaffirmed at the time of execution.
In this example, we define (and, the, draw) an airplane in a bitmap monocromatic resolution:
BITMAP ENABLE (2) airplaneImage = LOAD IMAGE("examples/air_attack_airplane.png") PUT IMAGE airplaneImage : REM use last graphic coordinates, normally (0,0)
This implies that the loading and the conversion of the images takes place according to a graphic modality deduced from the static execution of the code, while the drawing of the graphic elements from the dynamic execution. This can be a bit confusing in those programs that use more than one graphics mode.
As a general rule, the graphic elements must be defined within the chosen resolution, and therefore it is necessary to foresee a phase in which the program “explores” the modalities that it will then use, in order to define the graphic elements. Finally, remember that it is not possible to “mix” images defined for different graphic modes.
In this example, we define an airplane in a bitmap monocromatic resolution and, then, in a multicolor bitmap (we use the AS
keyword to avoid aliasing):
BITMAP ENABLE (2) airplaneImage = LOAD IMAGE("examples/air_attack_airplane1.png" AS "airplanebw") BITMAP ENABLE (16) airplaneImage = LOAD IMAGE("examples/air_attack_airplane2.png" AS "airplanecol")
We previously stated that images are represented in a way that makes drawing them efficient. However, it does not allow us to have maximum freedom in positioning, and this is necessary if we want to implement a believable moving animation.
In order to move images on the screen at single pixel resolution, it is necessary to assign the still images to a moving object, ie MOB
. MOB
is an acronym that stands for Moving OBjects. They are pixel surfaces (monochromatic or polychromatic, depending on how the images are defined) that can be moved via software or (where the hardware allows it) via hardware.
To assign an image to a MOB
you can use this syntax:
BITMAP ENABLE (2) airplaneImage = LOAD IMAGE("examples/air_attack_airplane1.png" AS "airplanebw") airplane = 0 MOB airplane, airplaneImage
Once defined as MOB
, this will still not be visible anyway. In fact, MOB
s start out as invisible and it is necessary to make them visible. To do this you must use the MOB SHOW
command.
BITMAP ENABLE (2) airplaneImage = LOAD IMAGE("examples/air_attack_airplane1.png" AS "airplanebw") airplane = 0 MOB airplane, airplaneImage MOB SHOW airplane
There is an even more synthetic syntax to define a MOB
and make it immediately visible:
MOB airplane, airplaneImage AT 16,16 VISIBLE
Obviously, you can omit the coordinates, as well.
The movement is implemented with the classic painter's algorithm: the MOB
s are drawn in a certain order and, after each move, the original background is restored in reverse order before proceeding with the drawing of the animation.
BITMAP ENABLE (2) airplaneImage = LOAD IMAGE("examples/air_attack_airplane1.png" AS "airplanebw") airplane = 0 MOB airplane, airplaneImage MOB SHOW airplane FOR i = (POSITION) 0 TO 42 STEP (POSITION) 1 MOB airplane AT (POSITION) 0, i MOB RENDER NEXT
Appropriate optimizations are put in place to avoid redesigning those areas of the screen where the MOBs have not moved, as well as (obviously) making use of the available hardware, if any.
Again, remember that ugBASIC is an isomorphic language, so there is no guarantee that the result will be the same on all platforms. This is the responsibility of the programmer. The language exposes the mechanisms of the target machine so that they can be exploited with the greatest possible efficiency.
Note how it is necessary to call the MOB RENDER
primitive so that the various MOB
s are actually drawn. This is a very important element in the logic of graphic objects in motion, because it allows to synchronize the movement according to the specific needs of the development.
In general, it is not recommended to redraw the screen with every movement, because the target computers are usually not fast enough to draw the various elements quickly. This generates what is called “flickering”, because the screen is updated while you are redrawing the scene.
To avoid this, it is possible to synchronize the drawing phase of the movable objects with the so-called “vertical blank” (VBL). ugBASIC has a very convenient mechanism to wait for a vertical blank (ie when the screen can be updated without being noticed). It is sufficient to extend the current syntax by using the ON VBL
expression:
BITMAP ENABLE (2) airplaneImage = LOAD IMAGE("examples/air_attack_airplane1.png" AS "airplanebw") airplane = 0 MOB airplane, airplaneImage MOB SHOW airplane FOR i = (POSITION) 0 TO 42 STEP (POSITION) 1 MOB airplane AT (POSITION) 0, i MOB RENDER ON VBL NEXT