ugBASIC User Manual

Images

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

Loading images

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")

The command support a set of modern image format, like:

  • JPEG baseline & progressive
  • PNG 1/2/4/8/16-bit-per-channel
  • TGA
  • BMP (non-1bpp, non-RLE)
  • PSD (composited view only, no extra channels, 8/16 bit-per-channel)
  • GIF
  • HDR (radiance rgbE format)
  • PIC (Softimage PIC)
  • PNM (PPM and PGM binary only)

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.

Drawing images

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

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, then, draw) an airplane in a bitmap monocromatic resolution:

BITMAP ENABLE (2)
airplaneImage = LOAD IMAGE("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("air_attack_airplane1.png" AS "airplanebw")
BITMAP ENABLE (16)
airplaneImage = LOAD IMAGE("air_attack_airplane2.png" AS "airplanecol")

Flipping images beta

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.



The flipping at load time is done by using 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

To flip an image vertically, use the FLIP IMAGE Y command:

BITMAP ENABLE (16)
airplaneImage = LOAD IMAGE("boy.png")
FLIP IMAGE Y airplaneImage

Finally, to simultaneously flip the image according to X and Y, you can use the FLIP IMAGE XY (or FLIP IMAGE YX) command:

BITMAP ENABLE (16)
airplaneImage = LOAD IMAGE("boy.png")
FLIP IMAGE XY airplaneImage

Note that ugBASIC also accepts this other form:

FLIP IMAGE airplaneImage X
FLIP IMAGE airplaneImage Y
FLIP IMAGE airplaneImage XY

Please note that the flipping operation may not be available if the image is BANKED, i.e. located in a virtual memory expansion, as can happen on some targets. To learn more, please click here.

Atlas

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 (beta) command. In that case, you need to provide the size of the single frame:

atlas := LOAD IMAGES("boy.png") FRAME SIZE( 32, 32 )



You can use the PUT IMAGE with the following syntax:

PUT IMAGE atlas FRAME 2 AT 0, 0

Note that ugBASIC does not check if the atlas has enough frames. If you are unsure, you can use the 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

Moreover, an animated GIF can be converted automatically in an atlas. In this case, there is no need to give the frame size, and you can use the FRAME SIZE AUTO syntax:

earth := LOAD IMAGES("earth_128x128_2colors.gif") FRAME SIZE AUTO

Finally, on some targets it is possible to load the atlas onto a memory expansion, to save resident (central) memory. To learn more, please click here.

Sequences

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 (beta) 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.

Transparency

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

Getting images

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

Advanced topics

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.

What is a buffer?

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.

So, what is an image?

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.

Beyond transparency

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.

Any problem?

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!