| TGX 1.1.3 A tiny 2D/3D graphics library optimized for 32 bits microcontrollers.

Loading...
Searching...
No Matches
Basic concepts.

The TGX library's main class is the tgx::Image class which, as its name suggests, encapsulates an image, that is a rectangular array of pixels of a given color type. The library provides an extensive set of 2D and 3D drawing procedures that operate on image objects.

All methods/classes are defined inside the `tgx` namespace. To use the library, just include `tgx.h`:

#include <tgx.h>
using namespace tgx; // optional, so we do not need to prefix all methods by tgx::
tgx.h
Main header file.

A minimal TGX framebuffer is just a pixel buffer plus an Image view over it:

tgx::RGB565 buffer[320 * 240];
tgx::Image<tgx::RGB565> im(buffer, 320, 240);
im.clear(tgx::RGB565_Black);
im.drawLine({0, 0}, {319, 239}, tgx::RGB565_Red);
tgx::RGB565_Red
const RGB565 RGB565_Red
Color red in RGB565 format.
tgx::Image
Image class [MAIN CLASS FOR THE 2D API].
Definition: Image.h:145
tgx::RGB565
Color in R5/G6/B5 format.
Definition: Color.h:216

# Color types

The library defines several color classes. Choosing the right one is mostly a question of memory, speed and intended use:

Type | Layout | Storage/alignment | Typical use
--- | --- | --- | ---
tgx::RGB565 | 16-bit RGB: 5 bits red, 6 bits green, 5 bits blue | Aligned as and convertible to `uint16_t` | Preferred type on MCU displays: compact, fast, and compatible with many embedded display drivers.
tgx::RGB24 | 24-bit RGB: 8 bits red, 8 bits green, 8 bits blue | No special alignment | Useful on CPU or when a simple 24-bit image format is convenient and no alpha channel is needed.
tgx::RGB32 | 32-bit RGBA: 8 bits per channel, pre-multiplied alpha | Aligned as and convertible to `uint32_t` | Useful when an alpha channel is needed, especially for pre-multiplied alpha blending.
tgx::RGBf | 96-bit RGB: 32-bit float per channel | Aligned as `float`, no alpha channel | Used for floating-point color computations, especially with the 3D API lighting/material code.
tgx::RGB64 | 64-bit RGBA: 16 bits per channel, pre-multiplied alpha | Aligned as and convertible to `uint64_t` | Use only when high precision and alpha are both needed; heavier than the usual MCU formats.
tgx::HSV | 96-bit HSV: 16 bits Hue, 16 bits Saturation, 16 bits Value | Partial support only | Mainly an intermediate color space for conversions or procedural color generation; slow for drawing operations.

Color types are handled just like usual basic types such as `int` or `char`. Any color type can be converted into any other color type.

Examples:

tgx::RGB565 color1; // create a 16-bit color, undefined value
tgx::RGB565 color2(8, 32, 16); // create a 16-bit color and set the color channels: 8 red (5 bits), 32 green (6 bits), 16 blue (5 bits)
tgx::RGB565 color3(0.25f, 0.5f, 0.5f); // the same as color2 but defined with floats in [0.0f, 1.0f] (works for any color type, irrespective of the channel color depth)
uint16_t c2 = (uint16_t)color3; // conversion to uint16_t.
tgx::RGB565 color4 = c2; // create a 16-bit color from uint16_t.
int8_t R = color4.R; //
int8_t G = color4.G; // access the raw channel values
int8_t B = color4.B; //
tgx::RGB32 color5(color2); // conversion: create a RGB32 color from RGB565, this yields RGB32(64, 128, 128, 255) - the alpha channel is set to fully opaque.
color5.setOpacity(0.5f); // adjust the alpha channel in order to make the color half transparent. Other channels are multiplied accordingly (pre-multiplied alpha).
tgx::RGB32
Color in R8/G8/B8/A8 format.
Definition: Color.h:1174
tgx::RGB565::G
uint16_t G
Green channel (6 bits)
Definition: Color.h:232
tgx::RGB565::R
uint16_t R
Red channel (5 bits)
Definition: Color.h:233
tgx::RGB565::B
uint16_t B
Blue channel (5 bits)
Definition: Color.h:231

Predefined colors: Black, White, Blue, Green, Purple, Orange, Cyan, Lime, Salmon, Maroon, Yellow, Magenta, Olive, Teal, Gray, Silver, Navy, Transparent.

Example:

tgx::RGB32_Yellow; // yellow color in RGB32
tgx::RGB565_Green; // green color in RGB565

For additional details on color manipulations, look directly into the color classes definitions in color.h.

## Blending and opacity parameter.

Color types tgx::RGB32 and tgx::RGB64 have an alpha channel which is used for alpha-blending during drawing operations.

Note
  Colors with an alpha channel are always assumed to have pre-multiplied alpha.

Other color types do not have an alpha channel but the library still supports simple blending operations through the optional `opacity` parameter added to all drawing primitives. This parameter ranges in `[0.0f, 1.0f]` with

- `opacity = 1.0f`: fully opaque drawing. This is the default value when the parameter is omitted.
- `opacity = 0.0f`: fully transparent drawing (so this draws nothing).

Example:

// assume below that `im` is a tgx::Image<tgx::RGB565> object
// the method Image::drawLine has signature drawLine(iVec2 P1, iVec2 P2, color_t color, float opacity).
im.drawLine({10, 20}, {30, 40}, tgx::RGB565_Red); // draw a fully opaque red line from (10,20) to (30, 40).
im.drawLine({10, 20}, {30, 40}, tgx::RGB565_Red, 0.5f); // here opacity=0.5f so we draw a half-transparent red line instead.

# Image class, memory layout and sub-images

The tgx::Image class is a lightweight view over an existing pixel buffer. It does not allocate memory and it does not own the pixels. It only records how to interpret a rectangular region of memory as an image. These four values are the whole state of an Image view:

Field | Meaning
--- | ---
`data()` | Pointer to the first visible pixel.
`lx()` | Width of the image, in pixels.
`ly()` | Height of the image, in pixels.
`stride()` | Distance, in pixels, between the beginning of two consecutive rows.

This design is important on embedded targets: the application controls where buffers live, for example in RAM, external memory or flash/ROM. As a general rule, the TGX library never performs dynamic memory allocation.

Warning
  Copying a tgx::Image object is a shallow copy: the new object points to the same pixel buffer. The pixel buffer must remain alive as long as an Image points to it. Deep copies are discussed later in Copying/converting images to different color types.

## Creating an image

The most common pattern is to create a pixel buffer, then attach an Image view to it directly in the constructor:

tgx::RGB565 buffer[320 * 240]; // allocate the image buffer (about 150 KB)
tgx::Image<tgx::RGB565> im(buffer, 320, 240); // default stride = width = 320

An image can also be default-constructed and attached to a buffer later:

tgx::RGB32 buffer32[320 * 240];
tgx::Image<tgx::RGB32> im; // invalid image, not attached to a buffer yet
im.set(buffer32, 320, 240); // the image is now valid
tgx::Image::set
void set(T *buffer, int lx, int ly, int stride=DEFAULT_STRIDE)
Set/update the image parameters.

Warning
  The following code is wrong. The returned Image points to a local buffer that disappears when the function returns:
  tgx::Image<tgx::RGB565> makeImage()
  {
  tgx::RGB565 local[100 * 100];
  return tgx::Image<tgx::RGB565>(local, 100, 100); // WRONG: dangling pointer !!!
  }

## Memory layout and stride

The position of a pixel inside an image is given in row-major order:

`Pixel(i, j) = buffer[i + stride*j]`

for 0 ≤ `i` < `lx` and 0 ≤ `j` < `ly`.

The `stride` is the distance, in pixels, between the beginning of one row and the beginning of the next row. For a standalone image, it is usually equal to `lx`. For a sub-image, it is often larger because the sub-image still points inside the original parent buffer:

Parent image buffer
row 0: [0,0] [1,0] [2,0] [3,0] [4,0] [5,0]
row 1: [0,1] [1,1] [2,1] [3,1] [4,1] [5,1]
row 2: [0,2] [1,2] [2,2] [3,2] [4,2] [5,2]
row 3: [0,3] [1,3] [2,3] [3,3] [4,3] [5,3]
Sub-image covering x = 1..3 and y = 1..2
row 1: [1,1] [2,1] [3,1]
row 2: [1,2] [2,2] [3,2]
sub-image lx = 3, ly = 2, but stride = 6 because the next row is still
6 pixels farther in the parent buffer.

## Sub-images

A sub-image is another Image object that points to a rectangular part of an existing image. It shares the same pixel buffer and clips drawing operations to its own rectangle. Drawing coordinates are local to the sub-image:

tgx::RGB32 buffer[320 * 240];
tgx::Image<tgx::RGB32> im(buffer, 320, 240);
// Region [20, 50] x [100, 200] in the parent image.
tgx::Image<tgx::RGB32> panel = im({20, 50, 100, 200});
panel.fillScreen(tgx::RGB32_Blue); // only modifies the panel region
panel.drawPixel({0, 0}, tgx::RGB32_Red); // top-left pixel of panel, not of im
panel.drawCircle({15, 20}, 12, tgx::RGB32_White);
tgx::RGB32_Red
const RGB32 RGB32_Red
Color red in RGB32 format.
tgx::RGB32_White
const RGB32 RGB32_White
Color white in RGB32 format.
tgx::Image::drawCircle
void drawCircle(iVec2 center, int r, color_t color, float opacity=TGX_DEFAULT_NO_BLENDING)
Draw a circle.
tgx::Image::drawPixel
TGX_INLINE void drawPixel(iVec2 pos, color_t color)
Set a pixel at a given position.
Definition: Image.h:455
tgx::Image::fillScreen
void fillScreen(color_t color)
Fill the whole image with a single color.

For this sub-image, `panel.data()` points inside `buffer`, `panel.lx()` is `31`, `panel.ly()` is `101`, and `panel.stride()` is still `320`, the stride of the parent image.

This is why TGX can create sub-images without copying pixels: it only changes the starting pointer, width, height and stride stored in the tgx::Image object.

See also: Image::set(), Image::crop(), Image::getCrop(), Image::operator(), Image::isValid(), Image::setInvalid(), Image::imageBox().

Note
  Creating images/sub-images is very fast and nearly cost-free, so users should not refrain from creating Image/sub-images whenever needed. It is the most efficient solution to clip/restrict any drawing operation to a given rectangular region of an image.

# Coordinates, vectors and boxes

TGX uses the usual framebuffer coordinate system: the pixel `(0,0)` is located at the top-left corner of the image, the X coordinate grows to the right, and the Y coordinate grows downward.

Pixel addressing varies slightly when using integer-valued coordinates vs floating point-valued coordinates:

Coordinate type | Meaning of `(i,j)` | Whole image box for an image of size `lx` x `ly`
--- | --- | ---
`iVec2`, `iBox2` | Pixel index/location | `iBox2(0, lx - 1, 0, ly - 1)`
`fVec2`, `fBox2` | Center of pixel `(i,j)` | `fBox2(-0.5f, lx - 0.5f, -0.5f, ly - 0.5f)`

For example, the floating point box occupied by pixel `(0,0)` is `fBox2(-0.5f, 0.5f, -0.5f, 0.5f)`, centered at `fVec2(0.0f, 0.0f)`.

The difference matters when mixing exact pixel primitives and anti-aliased or sub-pixel primitives. As a rule of thumb, use integer coordinates for exact pixel primitives and floating point coordinates for anti-aliased or sub-pixel primitives.

## Vector and box classes

The tgx library defines classes for 2D vectors and boxes:

Class | Meaning
--- | ---
tgx::iVec2 | Integer-valued 2D vector, used for pixel locations.
tgx::fVec2 | Floating point-valued 2D vector, used for sub-pixel precision.
tgx::iBox2 | Integer-valued 2D box, used to represent a rectangular pixel region.
tgx::fBox2 | Floating point-valued 2D box, used to represent a sub-pixel rectangular region.

Note
  3D variants for vectors and boxes are also available: tgx::iVec3, tgx::fVec3, tgx::iBox3, tgx::fBox3. See the 3D API tutorial for details.

Vectors and boxes support the usual operations: arithmetic (addition, scalar multiplication, dot product...), copy and type conversion.

tgx::iVec2 p(10, 30); // integer-valued vector
tgx::fVec2 q = p; // conversion to floating point: (10.0f, 30.0f)
tgx::fVec2 r = { 2.0f, 7.0f };
tgx::fVec2 s = (q + r) * 3.0f;
s.x = 4.0f; // direct access to coordinate values
tgx::Vec2
Generic 2D vector [specializations iVec2, fVec2, dVec2].
Definition: Vec2.h:64
tgx::Vec2::x
T x
'x' coordinate (first dimension)
Definition: Vec2.h:72

Boxes use the order `(minx, maxx, miny, maxy)`:

tgx::iBox2 B(40, 80, 20, 30); // integer box [40,80] x [20,30]
tgx::Box2
Generic 2D Box [specializations iBox2 , fBox2, dBox2].
Definition: Box2.h:151

Integer boxes are inclusive: both `min` and `max` coordinates belong to the box. Therefore, the width of the box above is `80 - 40 + 1 = 41` pixels. This differs from half-open rectangle conventions used by some other libraries.

Most drawing methods take vectors and boxes as input parameters instead of scalars. It is recommended to use initializer lists to make the code more readable. For example, write `{10, 20}` instead of `tgx::iVec2(10, 20)`.

// assume below that `im` is a tgx::Image<tgx::RGB32> object
im.drawCircle({10, 30}, 12, tgx::RGB32_Red);
im.drawCircleAA({50.0f, 60.0f}, 11.5f, tgx::RGB32_Blue);
im.fillRect({50, 60, 70, 80}, tgx::RGB32_Black);
tgx::RGB32_Blue
const RGB32 RGB32_Blue
Color blue in RGB32 format.
tgx::RGB32_Black
const RGB32 RGB32_Black
Color black in RGB32 format.
tgx::Image::fillRect
void fillRect(const iBox2 &B, color_t color, float opacity=TGX_DEFAULT_NO_BLENDING)
Draw a filled rectangle.
tgx::Image::drawCircleAA
void drawCircleAA(fVec2 center, float r, color_t color, float opacity=1.0f)
Draw a circle (high quality).

## Common beginner mistakes

- Do not let the pixel buffer go out of scope while an Image still points to it.
- Remember that sub-image coordinates are local to the sub-image.
- Remember that integer boxes are inclusive.
- Use the native channel range when constructing low-level colors such as `RGB565(31, 63, 31)`. Use float constructors such as `RGB565(1.0f, 1.0f, 1.0f)` when you want normalized color values.

# Storing images in C++ source files

The memory buffer an image points to may reside in flash/ROM and it is possible to define `const` images directly in `.cpp` files for easy inclusion in a project.

For example, the 10x10 image

can be stored as:

// macro to make the array more readable/compact.
#define C(val) tgx::RGB565((uint16_t)val)
// image data; the PROGMEM keyword ensures that the image is stored in FLASH memory.
const tgx::RGB565 smiley_data[] PROGMEM = {
C(0xffff), C(0xffff), C(0xf714), C(0xe609), C(0xdd40), C(0xdd40), C(0xe609), C(0xf714), C(0xffff), C(0xffff),
C(0xffff), C(0xee8f), C(0xddc6), C(0xeef1), C(0xfffb), C(0xfffb), C(0xeef1), C(0xddc6), C(0xee8f), C(0xffff),
C(0xeef4), C(0xdda5), C(0xf775), C(0xffd8), C(0xffd8), C(0xffd8), C(0xffd8), C(0xf775), C(0xdda5), C(0xeef4),
C(0xd5a9), C(0xee8d), C(0xff94), C(0x9b20), C(0xff94), C(0xff94), C(0x9b20), C(0xff94), C(0xee8d), C(0xd5a9),
C(0xc4a0), C(0xff50), C(0xff50), C(0xff50), C(0xff50), C(0xff50), C(0xff50), C(0xff50), C(0xff50), C(0xc4a0),
C(0xbc60), C(0xff0b), C(0xff0b), C(0xff0b), C(0xff0b), C(0xff0b), C(0xff0b), C(0xff0b), C(0xff0b), C(0xbc60),
C(0xc4e8), C(0xe5c4), C(0xfea6), C(0xaba0), C(0xfea6), C(0xfea6), C(0xaba0), C(0xfea6), C(0xe5c4), C(0xc4e8),
C(0xd612), C(0xc481), C(0xf622), C(0xdd40), C(0x9b20), C(0x9b20), C(0xdd40), C(0xf622), C(0xc481), C(0xd612),
C(0xf79e), C(0xc54d), C(0xbc60), C(0xdd60), C(0xfe40), C(0xfe40), C(0xdd60), C(0xbc60), C(0xc54d), C(0xf79e),
C(0xffff), C(0xef7d), C(0xcdd2), C(0xb4a8), C(0xaba0), C(0xaba0), C(0xb4a8), C(0xcdd2), C(0xef7d), C(0xffff) };
// image object, 10x10 image with smiley_data as pixel buffer.
const tgx::Image<tgx::RGB565> smiley(smiley_data, 10, 10);

The `/tools/` subdirectory of the library contains the `tgx_image.py` graphical converter and the advanced `cli_tools/tgx_image_cli.py` command-line converter. They turn classical image files (PNG/JPEG/BMP...) into TGX image headers or `.h + .cpp` pairs.

Note
  The TGX library also makes it easy to directly load PNG, JPEG and GIF images (which may be stored in RAM/FLASH or on another medium such as an SD card) by using bindings with external libraries. See section TGX extensions via external libraries for more details.

# Copying/converting images to different color types

Recall that a tgx::Image object is just a view into a buffer of pixels of a given color type. Thus, copies of image objects are shallow (they share the same image buffer / same color type).

The library provides methods to create "deep" copies and convert images to different color types:

- Method Image<color_t>::copyFrom(src_im, opacity) can be used to create deep copies of images and it can also change the color type and resize them (using bilinear interpolation).
- Method Image<color_t>::convert<color_dst>() can be used to change the color type of an image "in place".

tgx::RGB32 buf1[100*100]; // buffer for a 100x100 image in RGB32 color format
tgx::Image<tgx::RGB32> im1(buf1, 100, 100); // image that encapsulates the pixel buffer buf1
// ... draw some things on im1...
tgx::RGB24 buf2[50 * 50]; // buffer for a 50x50 image in RGB24 color format
tgx::Image<tgx::RGB24> im2(buf2, 50, 50); // image that encapsulates the pixel buffer buf2
// copy the content of im1/buf1 into im2/buf2, resize and perform color conversion automatically.
im2.copyFrom(im1);
// we can directly change the color type of im1 in place from RGB32 to RGB565
tgx::Image<tgx::RGB565> im3 = im1.convert<tgx::RGB565>();
// ok, now im3 has the same content as im1 had, but in RGB565 color format.
// Beware that im1 now contains 'garbage' (seen as RGB32 data) but writing
// on im1 will change the content of im3 as they share the same buffer !
tgx::Image::convert
Image< color_dst > convert()
Convert this image to another type.
tgx::RGB24
Color in R8/G8/B8 format.
Definition: Color.h:679

Note
  See also the methods for blitting sprites which may be used for copy and color type conversion.

# Where to go next

You now know how TGX images store pixels, how coordinates work, and how image objects refer to their pixel buffers.

Choose the next tutorial depending on what you want to draw:

Goal | Continue with
--- | ---
Draw pixels, lines, rectangles, circles, curves, sprites, text, image blits or 2D user-interface elements. | The 2D API tutorial
Render 3D primitives, meshes, lights, textures, Z-buffered scenes, cameras and projections. | The 3D API tutorial

Of course, projects can use both parts together: the 3D renderer draws into an image, and the 2D API can then overlay text, user-interface elements, debug information or sprites on top of the rendered frame.