3.2. VG Lite Library

3.2.1. Vector Graphics Description

Vector graphics are objects that are defined by:

  • instructions (such as line_to or curve_to),
  • coordinates (parameters of the instructions),
  • styles (plain color with or without transparency, gradients, pattern),

Vector graphics have the advantage over raster graphics in that they can be transformed without loss of quality.

Vector graphics are commonly used in vector fonts (i.e., TrueType) and SVG.

They are intrinsically different from the more common raster graphics file formats such as BMP, JPEG, or PNG.

They allow rendering complex objects with a constant amount of data.

3.2.2. Aim of the Library

The VG Lite foundation library provides access to the VG Lite functionalities of the platform. It has been designed to be as close as possible to the native VGLite library provided in the MIMXRT595-EVK SDK.

Note

The library does not respect the Java class naming convention (camel case): it uses the same naming convention as C library vglite. The functions have the same prototype as the vg_lite.h functions.

3.2.3. Importing the Library

The library can be retrieved by adding the following dependency to your module.ivy:

<dependency org="com.microej.api" name="vivante-vglite" rev="0.6.0" conf="provided->*" />

Note

The VGLite library is a foundation library. The platform provides it.

3.2.4. The Java API

The library is made of several units:

  • vg_lite: Implements the supported APIs of vg_lite.h:
    • vg_lite_clear: Fills a (partial) buffer with a specified color.
    • vg_lite_draw_path: Draws a path to a MicroUI GraphicsContext.
    • vg_lite_draw_gradient: Fills a path with a gradient.
  • vg_lite_path: This class represents a vg_lite_path structure. It also provides the necessary method to manipulate the data. The class holds a handle that is used to manage the structure natively.
  • vg_lite_matrix: This class represents a vg_lite_matrix structure. It also provides the necessary method to manipulate the data. The class holds a handle that is used to manage the structure natively. The class also provides the follwing APIs that are mapped on native matrix transformation functions:
    • vg_lite_identity: To set a matrix to identity.
    • vg_lite_translate: Translates a matrix.
    • vg_lite_scale: Scales a matrix.
    • vg_lite_rotate: Rotates a matrix.
    • vg_lite_perspective: Updates a matrix to apply perspective transformations.

Note

vg_lite_matrix and vg_lite_gradient are closable classes.

As they are doing dynamic allocation, it is essential to close them before they are dereferenced to provide memory leaks.

3.2.5. Matrix Transformation

Matrix operations should be executed in the reverse order of the expected result.

More information about matrix operations can be found at http://www.opengl-tutorial.org/beginners-tutorials/tutorial-3-matrices/ (For 2D Graphics, 3x3 matrices are used instead of 4x4 for 3D graphics).

For instance, consider the following code:

public static void draw(GraphicsContext gc, vg_lite_path path, int x, int y, float angle) {

        // Update transformation matrix
        vg_lite_matrix matrix = new vg_lite_matrix();
        vg_lite_matrix.vg_lite_translate(x, y, matrix); // 1. Translate
        vg_lite_matrix.vg_lite_rotate(angle, matrix); // 2. Rotate

        // Draw path
        vg_lite.vg_lite_draw_path(gc, path, vg_lite.VG_LITE_FILL_EVEN_ODD, matrix, vg_lite.VG_LITE_BLEND_NONE,
                        0xff000000 | Colors.GREEN);

        // Close matrix
        matrix.close();
}

The rendered object will be first translated by (x, y) and then rotated by angle (in degrees).

3.2.6. Vector Paths

A path is an array containing instructions and coordinates.

Consider the following code:

public class DrawPath {

        private static final byte[] RAW_PATH_DATA = new byte[] { 2, (byte) -100, (byte) -100, // move to -100, -100
                        4, (byte) 100, (byte) -100, // line to 100, -100
                        4, (byte) 100, (byte) 100, // line to 100, 100
                        0 // close path
        };

        public static void main(String[] args) throws InterruptedException {

                // Start application
                MicroUI.start();
                Display display = Display.getDisplay();
                GraphicsContext gc = display.getGraphicsContext();

                // Create path
                vg_lite_path path = new vg_lite_path(-100, -100, 100, 100, vg_lite_path.VG_LITE_HIGH, vg_lite_path.VG_LITE_S8,
                                RAW_PATH_DATA);

                // Paint background
                gc.setColor(Colors.WHITE);
                Painter.fillRectangle(gc, 0, 0, gc.getWidth(), gc.getHeight());

                // Update transformation matrix
                vg_lite_matrix matrix = new vg_lite_matrix();
                vg_lite_matrix.vg_lite_translate(gc.getWidth() / 2, gc.getHeight() / 2, matrix);

                // Draw path
                vg_lite.vg_lite_draw_path(gc, path, vg_lite.VG_LITE_FILL_EVEN_ODD, matrix, vg_lite.VG_LITE_BLEND_NONE,
                                0xff000000 | Colors.BLUE);

                // Flush content on display
                display.flush();

                // Ensure flush is completed before rendering next drawing
                display.waitFlushCompleted();

                // Stop application
                Thread.sleep(2000);
                matrix.close();
                MicroUI.stop();
        }
}

A blue right triangle will be rendered at coordinates (x, y).

../_images/vglite_example2.png

Note

This example shows how to create raw path data.

VGLite utils library provides helper utilities to draw paths.

3.2.7. Gradients

This library supports linear gradients.

On the GCNanoLiteV, Gradients are images of width 256 and height of 1 pixel, which are expanded to fill the path. An image contains smooth transitions between several color ramps. The stops define the position of each ramp. As it is a 256 width image (from 0 to 255), to position a color at 25%, the value 63 shall be used (255 / 4).

Transformation matrices like any graphic object can then transform the gradient.

Consider the following code:

public class DrawGradient {

        private static final int[] RAMPS = { 0xFFFF0000, 0xFF00FF00, 0xFF0000FF };
        private static final int[] STOPS = { 0, 127, 255 };

        private static final byte[] RAW_PATH_DATA = new byte[] { 2, (byte) -100, (byte) -100, // move to -100, -100
                        4, (byte) 100, (byte) -100, // line to 100, -100
                        4, (byte) 100, (byte) 100, // line to 100, 100
                        0 // close path
        };

        private static vg_lite_matrix pathMatrix;
        private static vg_lite_gradient gradient;
        private static int angle;
        private static boolean animating;

        public static void main(String[] args) throws InterruptedException {

                // Start application
                MicroUI.start();
                Display display = Display.getDisplay();
                GraphicsContext gc = display.getGraphicsContext();

                // Create path
                vg_lite_path path = new vg_lite_path(-100, -100, 100, 100, vg_lite_path.VG_LITE_HIGH, vg_lite_path.VG_LITE_S8,
                                RAW_PATH_DATA);

                pathMatrix = new vg_lite_matrix();
                gradient = new vg_lite_gradient();
                animating = true;

                int x = gc.getWidth() / 2;
                int y = gc.getHeight() / 2;

                while (animating) {

                        // Paint background
                        gc.setColor(Colors.WHITE);
                        Painter.fillRectangle(gc, 0, 0, gc.getWidth(), gc.getHeight());

                        // Animate: update matrix
                        animate(x, y);

                        // draw path
                        vg_lite.vg_lite_draw_gradient(gc, path, vg_lite.VG_LITE_FILL_EVEN_ODD, pathMatrix, gradient,
                                        vg_lite.VG_LITE_BLEND_NONE);

                        // Flush content on display
                        display.flush();

                        // Ensure flush is completed before rendering next drawing
                        display.waitFlushCompleted();
                }

                // Stop application
                pathMatrix.close();
                gradient.close();
                MicroUI.stop();
        }

        private static void animate(int x, int y) {

                // Update transformation matrix
                vg_lite_matrix.vg_lite_identity(pathMatrix);
                vg_lite_matrix.vg_lite_translate(x, y, pathMatrix); // 1. Translate
                vg_lite_matrix.vg_lite_rotate(angle++, pathMatrix); // 2. Rotate

                vg_lite_gradient.vg_lite_init_grad(gradient); // Initialization of the gradient object
                vg_lite_gradient.vg_lite_set_grad(gradient, RAMPS.length, RAMPS, STOPS); // Set gradients parameters
                vg_lite_gradient.vg_lite_update_grad(gradient); // Create the 256x1 image

                vg_lite_matrix gradientMatrix = gradient.getMatrix();
                vg_lite_matrix.vg_lite_identity(gradientMatrix);
                vg_lite_matrix.vg_lite_translate(x - 100, y - 100, gradientMatrix); // 2. Translate
                vg_lite_matrix.vg_lite_scale((float) 200 / 256, (float) 200 / 256, gradientMatrix); // 1. Scale

        }
}

A rotating right triangle with a static gradient will be rendered at coordinates (x, y).

../_images/vglite_example3.png

3.2.8. Clipping

Clipping is managed throw the MicrouiGraphicsContext.setClip API.

Update the render method by the following code:

[...]

// Animate: update matrix
animate(x, y);

// Set clip
gc.setClip(x - 100, y - 100, 200, 200);

// Draw path
vg_lite.vg_lite_draw_gradient(gc, path, vg_lite.VG_LITE_FILL_EVEN_ODD, pathMatrix, gradient,
                vg_lite.VG_LITE_BLEND_NONE);

[...]

The triangle is now clipped during rendering.

../_images/vglite_example3b.png

3.3. VG Lite util Library

MicroEJ also provides an addon library to help drawing Vector Graphics in the java source code.

3.3.1. Importing the Library

The library can be retrieved by adding the following dependency to your module.ivy:

<dependency org="com.microej.vivante" name="utils" rev="0.7.0" />

3.3.2. The Java API

The com.microej.vivante.utils library contains the classes PathBufferS8, PathBufferS16, PathBufferS32 and PathBufferFP32. Each of these classes allows generating paths in formats supported by the i.MX RT595 GPU quickly.

A buffer is wrapped by the instantiated objects that will then be used to create a vg_lite_path object. This buffer is updated by the drawing methods using the appropriate format.

Each class provides the following methods:

  • move: moves the cursor to the given absolute position,
  • moveRel: moves the cursor to the given relative position,
  • line: draws a line from the cursor position to the given absolute position and update the cursor,
  • lineRel: draws a line from the cursor position to the given relative position and update the cursor,
  • quad: draws a quadratic Bezier curve from the cursor position to the given absolute position and update the cursor,
  • quadRel: draws a quadratic Bezier curve from the cursor position to the given relative position and update the cursor,
  • cubic: draws a cubic Bezier curve from the cursor position to the given absolute position and update the cursor,
  • cubicRel: draws a cubic Bezier curve from the cursor position to the given relative position and update the cursor,
  • close: closes the current path,
  • toVGLitePath: exports the current path to a new vg_lite_path object,
  • updateVGLitePath: updates an existing vg_lite_path object with the current path.

Consider the following code:

public class DrawRedBall {

        private static final float TANGENT = 0.5522847498307933F; // Magic number to approximate circle arcs from Bezier

        public static void main(String[] args) throws InterruptedException {

                // Start application
                MicroUI.start();
                Display display = Display.getDisplay();
                GraphicsContext gc = display.getGraphicsContext();

                // Create path: approximation of a circle using Bezier cubic curves
                PathBufferFP32 pb = new PathBufferFP32();
                pb.move(0, -10);
                pb.cubicRel(10 * TANGENT, 0, 10, 10 - 10 * TANGENT, 10, 10);
                pb.cubicRel(0, 10 * TANGENT, -10 + 10 * TANGENT, 10, -10, 10);
                pb.cubicRel(-10 * TANGENT, 0, -10, -10 + 10 * TANGENT, -10, -10);
                pb.cubicRel(0, -10 * TANGENT, 10 - 10 * TANGENT, -10, 10, -10);
                vg_lite_path path = pb.toVGLitePath(-10, -10, 10, 10, vg_lite_path.VG_LITE_UPPER);

                // Paint background
                gc.setColor(Colors.WHITE);
                Painter.fillRectangle(gc, 0, 0, gc.getWidth(), gc.getHeight());

                // Update transformation matrix
                vg_lite_matrix matrix = new vg_lite_matrix();
                vg_lite_matrix.vg_lite_identity(matrix);
                vg_lite_matrix.vg_lite_translate(gc.getWidth() / 2, gc.getHeight() / 2, matrix); // 1. Translate
                vg_lite_matrix.vg_lite_scale(5, 5, matrix); // 2. Scale

                // Draw path
                vg_lite.vg_lite_draw_path(gc, path, vg_lite.VG_LITE_FILL_EVEN_ODD, matrix, vg_lite.VG_LITE_BLEND_NONE,
                                0xff000000 | Colors.RED);

                // Flush content on display
                display.flush();

                // Ensure flush is completed before rendering next drawing
                display.waitFlushCompleted();

                // Stop application
                Thread.sleep(2000);
                matrix.close();
                MicroUI.stop();
        }
}

A red circle is rendered.

../_images/vglite_example4.png

Note

The GPU does not support circle arc directly (see MicroUI Drawings). It is however possible to approximate circles with Bezier curves. See https://en.wikipedia.org/wiki/Composite_B%C3%A9zier_curve#Approximating_circular_arcs.

3.3.3. Going Further

One of the significant advantages of vector graphics is that animation of objects can be easy thanks to the matrix transformations without changing the original resource.

Update the following code in the DrawRedBall class:

public class DrawRedBall2 {

        private static final float TANGENT = 0.5522847498307933F; // Magic number to approximate circle arcs from Bezier

        private static final int MAX_X_SCALE = 7;
        private static final int MIN_X_SCALE = 4;
        private static final int MAX_Y_OFFSET = 100;
        private static final int Y_STEP = 5;
        private static final float SCALE_STEP = 1F / 3;
        private static final int STATE_DOWN = 0;
        private static final int STATE_BOUNCE_DOWN = 1;
        private static final int STATE_BOUNCE_UP = 2;
        private static final int STATE_UP = 3;

        private static vg_lite_matrix matrix;
        private static float xScale = 5;
        private static float yScale = 5;
        private static int state = STATE_DOWN;
        private static int yOffset = 0;
        private static boolean animating;

        public static void main(String[] args) {

                // Start application
                MicroUI.start();
                Display display = Display.getDisplay();
                GraphicsContext gc = display.getGraphicsContext();

                // Create path: approximation of a circle using Bezier cubic curves
                PathBufferFP32 pb = new PathBufferFP32();
                pb.move(0, -10);
                pb.cubicRel(10 * TANGENT, 0, 10, 10 - 10 * TANGENT, 10, 10);
                pb.cubicRel(0, 10 * TANGENT, -10 + 10 * TANGENT, 10, -10, 10);
                pb.cubicRel(-10 * TANGENT, 0, -10, -10 + 10 * TANGENT, -10, -10);
                pb.cubicRel(0, -10 * TANGENT, 10 - 10 * TANGENT, -10, 10, -10);
                vg_lite_path path = pb.toVGLitePath(-10, -10, 10, 10, vg_lite_path.VG_LITE_UPPER);

                matrix = new vg_lite_matrix();
                animating = true;

                int x = gc.getWidth() / 2;
                int y = gc.getHeight() / 2;

                while (animating) {

                        // Paint background
                        gc.setColor(Colors.WHITE);
                        Painter.fillRectangle(gc, 0, 0, gc.getWidth(), gc.getHeight());

                        // Animate: update matrix
                        animate(x, y);

                        // draw path
                        vg_lite.vg_lite_draw_path(gc, path, vg_lite.VG_LITE_FILL_EVEN_ODD, matrix, vg_lite.VG_LITE_BLEND_NONE,
                                        0xff000000 | Colors.RED);

                        // Flush content on display
                        display.flush();

                        // Ensure flush is completed before rendering next drawing
                        display.waitFlushCompleted();
                }

                // Stop application
                matrix.close();
                MicroUI.stop();
        }

        private static void animate(int x, int y) {

                switch (state) {
                default:
                case STATE_DOWN:
                        if (yOffset < MAX_Y_OFFSET) {
                                yOffset += Y_STEP;
                        } else {
                                state = STATE_BOUNCE_DOWN;
                        }
                        break;
                case STATE_BOUNCE_DOWN:
                        if (xScale < MAX_X_SCALE) {
                                xScale += SCALE_STEP;
                                yScale -= SCALE_STEP;
                                yOffset += Y_STEP;
                        } else {
                                state = STATE_BOUNCE_UP;
                        }
                        break;
                case STATE_BOUNCE_UP:
                        if (xScale > MIN_X_SCALE) {
                                xScale -= SCALE_STEP;
                                yScale += SCALE_STEP;
                                yOffset -= Y_STEP;
                        } else {
                                state = STATE_UP;
                        }
                        break;
                case STATE_UP:
                        if (yOffset > -MAX_Y_OFFSET) {
                                yOffset -= Y_STEP;
                        } else {
                                state = 0;
                        }
                        break;
                }
                // Update transformation matrix
                vg_lite_matrix.vg_lite_identity(matrix);
                vg_lite_matrix.vg_lite_translate(x, y + yOffset, matrix); // 1. Translate
                vg_lite_matrix.vg_lite_scale(xScale, yScale, matrix); // 2. Scale
        }
}

A ball bouncing up and down is rendered.

../_images/vglite_example4b.png

3.3.4. Limitations

The VGLite foundation library has the following limitations:

  • Rendering is not pixel accurate

  • The front-panel cannot render the following blending algorithms:

    • VG_LITE_BLEND_SCREEN
    • VG_LITE_BLEND_MULTIPLY
    • VG_LITE_BLEND_ADDITIVE

    To keep the front-panel operational when using these blending modes, VG_LITE_BLEND_SRC_OVER is used instead and a warning is displayed in the console.

  • The matrix transformation vg_lite_perspective is not available yet.