pebble/devsite/source/_guides/graphics-and-animations/drawing-primitives-images-and-text.md
2025-02-24 18:58:29 -08:00

8.1 KiB

title description guide_group order
Drawing Primitives, Images and Text How to draw primitive shapes, image, and text onto the Graphics Context. graphics-and-animations 1

While Layer types such as TextLayer and BitmapLayer allow easy rendering of text and bitmaps, more precise drawing can be achieved through the use of the Graphics Context APIs. Custom drawing of primitive shapes such as line, rectangles, and circles is also supported. Clever use of these functions can remove the need to pre-prepare bitmap images for many UI elements and icons.

Obtaining a Drawing Context

All custom drawing requires a GContext instance. These cannot be created, and are only available inside a LayerUpdateProc. This update procedure is simply a function that is called when a Layer is to be rendered, and is defined by the developer as opposed to the system. For example, a BitmapLayer is simply a Layer with a LayerUpdateProc abstracted away for convenience by the SDK.

First, create the Layer that will have a custom drawing procedure:

static Layer *s_canvas_layer;

Allocate the Layer during Window creation:

GRect bounds = layer_get_bounds(window_get_root_layer(window));

// Create canvas layer
s_canvas_layer = layer_create(bounds);

Next, define the LayerUpdateProc according to the function specification:

static void canvas_update_proc(Layer *layer, GContext *ctx) {
  // Custom drawing happens here!

}

Assign this procedure to the canvas layer and add it to the Window to make it visible:

// Assign the custom drawing procedure
layer_set_update_proc(s_canvas_layer, canvas_update_proc);

// Add to Window
layer_add_child(window_get_root_layer(window), s_canvas_layer);

From now on, every time the Layer needs to be redrawn (for example, if other layer geometry changes), the LayerUpdateProc will be called to allow the developer to draw it. It can also be explicitly marked for redrawing at the next opportunity:

// Redraw this as soon as possible
layer_mark_dirty(s_canvas_layer);

Drawing Primitive Shapes

The Graphics Context API allows drawing and filling of lines, rectangles, circles, and arbitrary paths. For each of these, the colors of the output can be set using the appropriate function:

// Set the line color
graphics_context_set_stroke_color(ctx, GColorRed);

// Set the fill color
graphics_context_set_fill_color(ctx, GColorBlue);

In addition, the stroke width and antialiasing mode can also be changed:

// Set the stroke width (must be an odd integer value)
graphics_context_set_stroke_width(ctx, 5);

// Disable antialiasing (enabled by default where available)
graphics_context_set_antialiased(ctx, false);

Lines

Drawing a simple line requires only the start and end positions, expressed as GPoint values:

GPoint start = GPoint(10, 10);
GPoint end = GPoint(40, 60);

// Draw a line
graphics_draw_line(ctx, start, end);

Rectangles

Drawing a rectangle requires a bounding GRect, as well as other parameters if it is to be filled:

GRect rect_bounds = GRect(10, 10, 40, 60);

// Draw a rectangle
graphics_draw_rect(ctx, rect_bounds);

// Fill a rectangle with rounded corners
int corner_radius = 10;
graphics_fill_rect(ctx, rect_bounds, corner_radius, GCornersAll);

It is also possible to draw a rounded unfilled rectangle:

// Draw outline of a rounded rectangle
graphics_draw_round_rect(ctx, rect_bounds, corner_radius);

Circles

Drawing a circle requries its center GPoint and radius:

GPoint center = GPoint(25, 25);
uint16_t radius = 50;

// Draw the outline of a circle
graphics_draw_circle(ctx, center, radius);

// Fill a circle
graphics_fill_circle(ctx, center, radius);

In addition, it is possble to draw and fill arcs. In these cases, the GOvalScaleMode determines how the shape is adjusted to fill the rectangle, and the cartesian angle values are transformed to preserve accuracy:

int32_t angle_start = DEG_TO_TRIGANGLE(0);
int32_t angle_end = DEG_TO_TRIGANGLE(45);

// Draw an arc
graphics_draw_arc(ctx, rect_bounds, GOvalScaleModeFitCircle, angle_start, 
                                                                    angle_end);

Lastly, a filled circle with a sector removed can also be drawn in a similar manner. The value of inset_thickness determines the inner inset size that is removed from the full circle:

uint16_t inset_thickness = 10; 

// Fill a radial section of a circle
graphics_fill_radial(ctx, rect_bounds, GOvalScaleModeFitCircle, inset_thickness,
                                                        angle_start, angle_end);

For more guidance on using round elements in apps, watch the presentation given at the 2015 Developer Retreat on developing for Pebble Time Round.

Bitmaps

Manually drawing GBitmap images with the Graphics Context API is a simple task, and has much in common with the alternative approach of using a BitmapLayer (which provides additional convenience funcionality).

The first step is to load the image data from resources (read {% guide_link app-resources/images %} to learn how to include images in a Pebble project):

static GBitmap *s_bitmap;
// Load the image data
s_bitmap = gbitmap_create_with_resource(RESOURCE_ID_EXAMPLE_IMAGE);

When the appropriate LayerUpdateProc is called, draw the image inside the desired rectangle:

Note: Unlike BitmapLayer, the image will be drawn relative to the Layer's origin, and not centered.

// Get the bounds of the image
GRect bitmap_bounds = gbitmap_get_bounds(s_bitmap);

// Set the compositing mode (GCompOpSet is required for transparency)
graphics_context_set_compositing_mode(ctx, GCompOpSet);

// Draw the image
graphics_draw_bitmap_in_rect(ctx, s_bitmap, bitmap_bounds);

Once the image is no longer needed (i.e.: the app is exiting), free the data:

// Destroy the image data
gbitmap_destroy(s_bitmap);

Drawing Text

Similar to the TextLayer UI component, a LayerUpdateProc can also be used to draw text. Advantages can include being able to draw in multiple fonts with only one Layer and combining text with other drawing operations.

The first operation to perform inside the LayerUpdateProc is to get or load the font to be used for drawing and set the text's color:

// Load the font
GFont font = fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD);
// Set the color
graphics_context_set_text_color(ctx, GColorBlack);

Next, determine the bounds that will guide the text's position and overflow behavior. This can either be the size of the Layer, or a more precise bounds of the text itself. This information can be useful for drawing multiple text items after one another with automatic spacing.

char *text = "Example test string for the Developer Website guide!";

// Determine a reduced bounding box
GRect layer_bounds = layer_get_bounds(layer);
GRect bounds = GRect(layer_bounds.origin.x, layer_bounds.origin.y,
                     layer_bounds.size.w / 2, layer_bounds.size.h);

// Calculate the size of the text to be drawn, with restricted space
GSize text_size = graphics_text_layout_get_content_size(text, font, bounds,
                              GTextOverflowModeWordWrap, GTextAlignmentCenter);

Finally, the text can be drawn into the appropriate bounding rectangle:

// Draw the text
graphics_draw_text(ctx, text, font, bounds, GTextOverflowModeWordWrap, 
                                            GTextAlignmentCenter, NULL);