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 and to draw them on the screen.
Loading
Drawing
Flipping
Atlas
Sequence
Transparency
Getting
Advanced
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 environment and it returns a value of type
IMAGE
, which can then be used.
themill := LOAD IMAGE("themill.png")
As long as it respects the limitations of the hardware, the image will
be converted into a way that can be efficiently drawn
on the screen. It could be converted, i.e., into indexed palette, and can be
rescaled as well.
Note that the term "load" does not imply the action performed by the executable,
but the action done by the compiler. The compiler, in fact, will take the file,
convert it into a format suitable for the target and graphic mode, and it stores
in the data segment of the program. As a result, there is no type of access
to a disk, tape or other media. For this type of operation, ugBASIC
provides other primitives, which are covered in another chapter. 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("air_attack_airplane.png")
airplaneImage2 = LOAD IMAGE("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("air_attack_airplane.png" AS "airplane1")
airplaneImage2 = LOAD IMAGE("air_attack_airplane.png" AS "airplane2")
In order to avoid useless copies, we suggest to use the :=
operator:
REM airplaneImage1 and airplaneImage2 are the very same buffer!
airplaneImage1 := LOAD IMAGE("air_attack_airplane.png" AS "airplane1")
airplaneImage2 := LOAD IMAGE("air_attack_airplane.png" AS "airplane2")
Finally, on some targets it is possible to load the image onto a memory expansion,
to save resident (central) memory. To learn more, please click here.
The ugBASIC language offers a primitive for drawing images,
and it's called PUT IMAGE
.
airplane := LOAD IMAGE("air_attack_airplane.png")
PUT IMAGE airplane AT 0,0
PUT IMAGE airplane AT 16,16
PUT IMAGE airplane AT 32,32
BITMAP ENABLE (2)
airplaneImage = LOAD IMAGE("air_attack_airplane.png")
PUT IMAGE airplaneImage : REM use last graphic coordinates, normally (0,0)
AS
keyword
to avoid aliasing):BITMAP ENABLE (2)
airplaneImage = LOAD IMAGE("air_attack_airplane1.png" AS "airplanebw")
BITMAP ENABLE (16)
airplaneImage = LOAD IMAGE("air_attack_airplane2.png" AS "airplanecol")
You can flip an image vertically, horizontally, or in both directions.
The operation can be carried out both when the image is loaded (therefore
with a "fixed" flip so to speak) and, on some targets, also during execution.
FLIP X
, FLIP Y
or FLIP XY
after the LOAD IMAGE
command. At runtime, the operation is carried out with the FLIP IMAGE
command.
To flip an image horizontally, use the FLIP IMAGE X
command:BITMAP ENABLE(16)
CLS
boy := LOAD IMAGE("boy.png" )
PUT IMAGE boy AT 0, 0
FLIP XY IMAGE boy
PUT IMAGE boy AT 0, 48
FLIP IMAGE Y
command:BITMAP ENABLE (16)
airplaneImage = LOAD IMAGE("boy.png")
FLIP IMAGE Y airplaneImage
FLIP IMAGE XY
(or FLIP IMAGE YX
) command:BITMAP ENABLE (16)
airplaneImage = LOAD IMAGE("boy.png")
FLIP IMAGE XY airplaneImage
FLIP IMAGE airplaneImage X
FLIP IMAGE airplaneImage Y
FLIP IMAGE airplaneImage XY
BANKED
,
i.e. located in a virtual memory expansion, as can happen on some targets.
To learn more, please click here.
The ugBASIC language allows you to load an atlas
("image collection") in a single operation. The advantage of atlases is
that they spatially compact multiple frames into a single image, which
can then be selected when drawing an image with the PUT IMAGE
command.
To read an atlas simply use the LOAD IMAGES
or LOAD ATLAS
command. In that case, you need to provide the size of the single frame:
atlas := LOAD IMAGES("boy.png") FRAME SIZE( 32, 32 )
PUT IMAGE
with the following syntax:PUT IMAGE atlas FRAME 2 AT 0, 0
FRAMES( )
function
to obtain the number of frames inside an atlas. Note that frames are 0 based, i.e.
the first frame is at 0, the second at 1, and so on. So:FOR x = 0 TO FRAMES(atlas)-1
PUT IMAGE atlas FRAME x
NEXT x
FRAME SIZE AUTO
syntax:earth := LOAD IMAGES("earth_128x128_2colors.gif") FRAME SIZE AUTO
The ugBASIC language allows you to load multiple atlas
("image sequences") in a single operation. The advantage of sequences is
that they spatially compact multiple horizontal atlas into a single vertical
stacked image, which can then be selected when drawing an image with
the PUT IMAGE
command.
To read a sequence simply use the LOAD SEQUENCE
command. Again, you need to provide the size of the single frame:
sequence := LOAD SEQUENCE("boy.png") FRAME SIZE( 32, 32 )
PUT IMAGE atlas SEQUENCE 1 FRAME 2 AT 0, 0
Also in this case, ugBASIC does not check if the sequence has enough
atlas and frames. If you are unsure, you can use the SEQUENCES( )
function
to obtain the number of sequences inside a sequence. Note that sequence are 0 based, i.e.
the first frame is at 0, the second at 1, and so on. So:
FOR x = 0 TO SEQUENCES(atlas)-1
FOR y = 0 TO FRAMES(atlas)-1
PUT IMAGE atlas SEQUENCE x FRAME y
NEXT y
NEXT x
Finally, on some targets it is possible to load the sequence onto a memory expansion,
to save resident (central) memory. To learn more, please click here.
When you draw an image, you can draw it transparently. To do this it is
necessary that the original image is in a format that maintains this
information, such as the PNG or RGBA or GIF format. In this case,
ugBASIC
will avoid modifying the pixel during drawing
if it is transparent. To enable this type of functionality, you
need to use the TRANSPARENCY
command:
PUT IMAGE boy AT x, y WITH TRANSPARENCY
With the same syntax with which images can be drawn, they can be generated
by capturing them from the visible screen. This is done with the
GET IMAGE
command:
GET IMAGE image FROM x, y
In order to work, this command must have an image, which will receive the
graphic and color data of the image thus obtained. To obtain this image,
you can use the NEW IMAGE
command.
dest := NEW IMAGE(16, 16)
GET IMAGE dest FROM 0, 0
PUT IMAGE dest AT 16, 16
Note that, just as it is possible to create and get a single image, it is obviously
possible to generate atlases and get single frames inside it. In this case, it is
necessary to indicate the number of frames that the atlas will have.
newAtlas := NEW ATLAS(10, 16, 16) : ' 10 frames of 16x16 pixel
GET IMAGE newAtlas FRAME 0 FROM 0, 0
GET IMAGE newAtlas FRAME 1 FROM 16, 0
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. The "content" of the BUFFER
is not well defined, because it depends on the target.
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) #[10000455aa55aa55aa55]
Although the actual content may vary, the format assumes that
the first two bytes represent the width (in pixels) and
the third the height (in pixels) (currently: $0010×$04 = 16x4 pixel),
while the rest of the sequence represents the graphic content.
Recall that ugBASIC is an isomorphic language,
we have that 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.
While transparency is a very powerful feature for graphically representing motion and effects, sometimes you need to go further. For example, there could be an advantage if the drawn image were first "decontourned" using appropriate masking. To do this you can take advantage of the blitting capabilities of ugBASIC. To find out more you can click here.
If you have found a problem, if you think there is a bug or, more
simply, you would like something to be improved, write a topic on the official forum, or open an issue on GitHub.
Thank you!