mirror of
https://github.com/google/pebble.git
synced 2025-03-26 05:09:05 +00:00
467 lines
14 KiB
Markdown
467 lines
14 KiB
Markdown
|
---
|
||
|
# Copyright 2025 Google LLC
|
||
|
#
|
||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
# you may not use this file except in compliance with the License.
|
||
|
# You may obtain a copy of the License at
|
||
|
#
|
||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||
|
#
|
||
|
# Unless required by applicable law or agreed to in writing, software
|
||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
# See the License for the specific language governing permissions and
|
||
|
# limitations under the License.
|
||
|
|
||
|
layout: tutorials/tutorial
|
||
|
tutorial: advanced
|
||
|
tutorial_part: 1
|
||
|
|
||
|
title: Vector Animations
|
||
|
description: |
|
||
|
How to use vector images in icons and animations.
|
||
|
permalink: /tutorials/advanced/vector-animations/
|
||
|
generate_toc: true
|
||
|
platform_choice: true
|
||
|
platforms:
|
||
|
- basalt
|
||
|
- chalk
|
||
|
- diorite
|
||
|
- emery
|
||
|
---
|
||
|
|
||
|
Some of the best Pebble apps make good use of the ``Animation`` and the
|
||
|
[`Graphics Context`](``Graphics``) to create beautiful and eye-catching user
|
||
|
interfaces that look better than those created with just the standard ``Layer``
|
||
|
types.
|
||
|
|
||
|
Taking a good design a step further may involve using the ``Draw Commands`` API
|
||
|
to load vector icons and images, and to animate them on a point-by-point basis
|
||
|
at runtime. An additional capability of the ``Draw Commands`` API is the draw
|
||
|
command sequence, allowing multiple frames to be incorporated into a single
|
||
|
resource and played out frame by frame.
|
||
|
|
||
|
This tutorial will guide you through the process of using these types of image
|
||
|
files in your own projects.
|
||
|
|
||
|
|
||
|
## What Are Vector Images?
|
||
|
|
||
|
As opposed to bitmaps which contain data for every pixel to be drawn, a vector
|
||
|
file contains only instructions about points contained in the image and how to
|
||
|
draw lines connecting them up. Instructions such as fill color, stroke color,
|
||
|
and stroke width are also included.
|
||
|
|
||
|
Vector images on Pebble are implemented using the ``Draw Commands`` APIs, which
|
||
|
load and display PDC (Pebble Draw Command) images and sequences that contain
|
||
|
sets of these instructions. An example is the weather icon used in weather
|
||
|
timeline pins. The benefit of using vector graphics for this icon is that is
|
||
|
allows the image to stretch in the familiar manner as it moves between the
|
||
|
timeline view and the pin detail view:
|
||
|
|
||
|

|
||
|
|
||
|
By including two or more vector images in a single file, an animation can be
|
||
|
created to enable fast and detailed animated sequences to be played. Examples
|
||
|
can be seen in the Pebble system UI, such as when an action is completed:
|
||
|
|
||
|

|
||
|
|
||
|
The main benefits of vectors over bitmaps for simple images and icons are:
|
||
|
|
||
|
* Smaller resource size - instructions for joining points are less memory
|
||
|
expensive than per-pixel bitmap data.
|
||
|
|
||
|
* Flexible rendering - vector images can be rendered as intended, or manipulated
|
||
|
at runtime to move the individual points around. This allows icons to appear
|
||
|
more organic and life-like than static PNG images. Scaling and distortion is
|
||
|
also made possible.
|
||
|
|
||
|
* Longer animations - a side benefit of taking up less space is the ability to
|
||
|
make animations longer.
|
||
|
|
||
|
However, there are also some drawbacks to choosing vector images in certain
|
||
|
cases:
|
||
|
|
||
|
* Vector files require more specialized tools to create than bitmaps, and so are
|
||
|
harder to produce.
|
||
|
|
||
|
* Complicated vector files may take more time to render than if they were simply
|
||
|
drawn per-pixel as a bitmap, depending on the drawing implementation.
|
||
|
|
||
|
|
||
|
## Creating Compatible Files
|
||
|
|
||
|
The file format of vector image files on Pebble is the PDC (Pebble Draw Command)
|
||
|
format, which includes all the instructions necessary to allow drawing of
|
||
|
vectors. These files are created from compatible SVG (Scalar Vector Graphics)
|
||
|
files using the
|
||
|
[`svg2pdc`]({{site.links.examples_org}}/cards-example/blob/master/tools/svg2pdc.py)
|
||
|
tool.
|
||
|
|
||
|
<div class="alert alert--fg-white alert--bg-dark-red">
|
||
|
Pebble Draw Command files can only be used from app resources, and cannot be
|
||
|
created at runtime.
|
||
|
</div>
|
||
|
|
||
|
To convert an SVG file to a PDC image of the same name:
|
||
|
|
||
|
```bash
|
||
|
$ python svg2pdc.py image.svg
|
||
|
```
|
||
|
|
||
|
To create a PDCS (Pebble Draw Command Sequence) from individual SVG frames,
|
||
|
specify the directory containing the frames with the `--sequence` flag when
|
||
|
running `svg2pdc`:
|
||
|
|
||
|
```bash
|
||
|
$ ls frames/
|
||
|
1.svg 2.svg 3.svg
|
||
|
4.svg 5.svg
|
||
|
|
||
|
$ python svg2pdc.py --sequence frames/
|
||
|
```
|
||
|
|
||
|
In the example above, this will create an output file in the `frames` directory
|
||
|
called `frames.pdc` that contains draw command data for the complete animation.
|
||
|
|
||
|
<div class="alert alert--fg-white alert--bg-dark-red">
|
||
|
{% markdown %}
|
||
|
**Limitations**
|
||
|
|
||
|
The `svg2pdc` tool currently supports SVG files that use **only** the following
|
||
|
elements: `g`, `layer`, `path`, `rect`, `polyline`, `polygon`, `line`, `circle`.
|
||
|
|
||
|
We recommend using Adobe Illustrator to create compatible SVG icons and images.
|
||
|
{% endmarkdown %}
|
||
|
</div>
|
||
|
|
||
|
For simplicity, compatible image and sequence files will be provided for you to
|
||
|
use in your own project.
|
||
|
|
||
|
|
||
|
### PDC icons
|
||
|
|
||
|
Example PDC image files are available for the icons listed in
|
||
|
[*App Assets*](/guides/app-resources/app-assets/).
|
||
|
These are ideal for use in many common types of apps, such as notification or
|
||
|
weather apps.
|
||
|
|
||
|
[Download PDC icon files >{center,bg-lightblue,fg-white}]({{ site.links.s3_assets }}/assets/other/pebble-timeline-icons-pdc.zip)
|
||
|
|
||
|
|
||
|
## Getting Started
|
||
|
|
||
|
^CP^ Begin a new [CloudPebble]({{ site.links.cloudpebble }}) project using the
|
||
|
blank template and add code only to push an initial ``Window``, such as the
|
||
|
example below:
|
||
|
|
||
|
^LC^ Begin a new project using `pebble new-project` and create a simple app that
|
||
|
pushes a blank ``Window``, such as the example below:
|
||
|
|
||
|
```c
|
||
|
#include <pebble.h>
|
||
|
|
||
|
static Window *s_main_window;
|
||
|
|
||
|
static void main_window_load(Window *window) {
|
||
|
Layer *window_layer = window_get_root_layer(window);
|
||
|
GRect bounds = layer_get_bounds(window_layer);
|
||
|
|
||
|
}
|
||
|
|
||
|
static void main_window_unload(Window *window) {
|
||
|
|
||
|
}
|
||
|
|
||
|
static void init() {
|
||
|
s_main_window = window_create();
|
||
|
window_set_window_handlers(s_main_window, (WindowHandlers) {
|
||
|
.load = main_window_load,
|
||
|
.unload = main_window_unload,
|
||
|
});
|
||
|
window_stack_push(s_main_window, true);
|
||
|
}
|
||
|
|
||
|
static void deinit() {
|
||
|
window_destroy(s_main_window);
|
||
|
}
|
||
|
|
||
|
int main() {
|
||
|
init();
|
||
|
app_event_loop();
|
||
|
deinit();
|
||
|
}
|
||
|
```
|
||
|
|
||
|
|
||
|
## Drawing a PDC Image
|
||
|
|
||
|
For this tutorial, use the example
|
||
|
[`weather_image.pdc`](/assets/other/weather_image.pdc) file provided.
|
||
|
|
||
|
^CP^ Add the PDC file as a project resource using the 'Add new' under
|
||
|
'Resources' on the left-hand side of the CloudPebble editor, with an
|
||
|
'Identifier' of `WEATHER_IMAGE`, and a type of 'raw binary blob'. The file is
|
||
|
assumed to be called `weather_image.pdc`.
|
||
|
|
||
|
^LC^ Add the PDC file to your project resources in `package.json` as shown
|
||
|
below. Set the 'name' field to `WEATHER_IMAGE`, and the 'type' field to `raw`.
|
||
|
The file is assumed to be called `weather_image.pdc`:
|
||
|
|
||
|
<div class="platform-specific" data-sdk-platform="local">
|
||
|
{% highlight {} %}
|
||
|
"media": [
|
||
|
{
|
||
|
"type": "raw",
|
||
|
"name": "WEATHER_IMAGE",
|
||
|
"file": "weather_image.pdc"
|
||
|
}
|
||
|
]
|
||
|
{% endhighlight %}
|
||
|
</div>
|
||
|
|
||
|
^LC^ Drawing a Pebble Draw Command image is just as simple as drawing a normal PNG
|
||
|
image to a graphics context, requiring only one draw call. First, load the
|
||
|
`.pdc` file from resources, for example with the `name` defined as
|
||
|
`WEATHER_IMAGE`, as shown below.
|
||
|
|
||
|
^CP^ Drawing a Pebble Draw Command image is just as simple as drawing a normal
|
||
|
PNG image to a graphics context, requiring only one draw call. First, load the
|
||
|
`.pdc` file from resources, for example with the 'Identifier' defined as
|
||
|
`WEATHER_IMAGE`. This will be available in code as `RESOURCE_ID_WEATHER_IMAGE`,
|
||
|
as shown below.
|
||
|
|
||
|
Declare a pointer of type ``GDrawCommandImage`` at the top of the file:
|
||
|
|
||
|
```c
|
||
|
static GDrawCommandImage *s_command_image;
|
||
|
```
|
||
|
|
||
|
Create and assign the ``GDrawCommandImage`` in `init()`, before calling
|
||
|
`window_stack_push()`:
|
||
|
|
||
|
```nc|c
|
||
|
static void init() {
|
||
|
/* ... */
|
||
|
|
||
|
// Create the object from resource file
|
||
|
s_command_image = gdraw_command_image_create_with_resource(RESOURCE_ID_WEATHER_IMAGE);
|
||
|
|
||
|
/* ... */
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Next, define the ``LayerUpdateProc`` that will be used to draw the PDC image:
|
||
|
|
||
|
```c
|
||
|
static void update_proc(Layer *layer, GContext *ctx) {
|
||
|
// Set the origin offset from the context for drawing the image
|
||
|
GPoint origin = GPoint(10, 20);
|
||
|
|
||
|
// Draw the GDrawCommandImage to the GContext
|
||
|
gdraw_command_image_draw(ctx, s_command_image, origin);
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Next, create a ``Layer`` to display the image:
|
||
|
|
||
|
```c
|
||
|
static Layer *s_canvas_layer;
|
||
|
```
|
||
|
|
||
|
Next, set the ``LayerUpdateProc`` that will do the rendering and add it to the
|
||
|
desired ``Window``:
|
||
|
|
||
|
```c
|
||
|
static void main_window_load(Window *window) {
|
||
|
|
||
|
/* ... */
|
||
|
|
||
|
// Create the canvas Layer
|
||
|
s_canvas_layer = layer_create(GRect(30, 30, bounds.size.w, bounds.size.h));
|
||
|
|
||
|
// Set the LayerUpdateProc
|
||
|
layer_set_update_proc(s_canvas_layer, update_proc);
|
||
|
|
||
|
// Add to parent Window
|
||
|
layer_add_child(window_layer, s_canvas_layer);
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Finally, don't forget to free the memory used by the ``Window``'s sub-components
|
||
|
in `main_window_unload()`:
|
||
|
|
||
|
```c
|
||
|
static void main_window_unload(Window *window) {
|
||
|
layer_destroy(s_canvas_layer);
|
||
|
gdraw_command_image_destroy(s_command_image);
|
||
|
}
|
||
|
```
|
||
|
|
||
|
When run, the PDC image will be loaded, and rendered in the ``LayerUpdateProc``.
|
||
|
To put the image into contrast, we will finally change the ``Window`` background
|
||
|
color after `window_create()`:
|
||
|
|
||
|
```c
|
||
|
window_set_background_color(s_main_window, GColorBlueMoon);
|
||
|
```
|
||
|
|
||
|
The result will look similar to the example shown below.
|
||
|
|
||
|

|
||
|
|
||
|
|
||
|
## Playing a PDC Sequence
|
||
|
|
||
|
The ``GDrawCommandSequence`` API allows developers to use vector graphics as
|
||
|
individual frames in a larger animation. Just like ``GDrawCommandImage``s, each
|
||
|
``GDrawCommandFrame`` is drawn to a graphics context in a ``LayerUpdateProc``.
|
||
|
|
||
|
For this tutorial, use the example
|
||
|
[`clock_sequence.pdc`](/assets/other/clock_sequence.pdc) file provided.
|
||
|
|
||
|
Begin a new app, with a C file containing the [template](#getting-started) provided above.
|
||
|
|
||
|
^CP^ Next, add the file as a `raw` resource in the same way as for a PDC image,
|
||
|
for example with an `Identifier` specified as `CLOCK_SEQUENCE`.
|
||
|
|
||
|
^LC^ Next, add the file as a `raw` resource in the same way as for a PDC image,
|
||
|
for example with the `name` field specified in `package.json` as
|
||
|
`CLOCK_SEQUENCE`.
|
||
|
|
||
|
<div class="platform-specific" data-sdk-platform="local">
|
||
|
{% highlight {} %}
|
||
|
"media": [
|
||
|
{
|
||
|
"type": "raw",
|
||
|
"name": "CLOCK_SEQUENCE",
|
||
|
"file": "clock_sequence.pdc"
|
||
|
}
|
||
|
]
|
||
|
{% endhighlight %}
|
||
|
</div>
|
||
|
|
||
|
Load the PDCS in your app by first declaring a ``GDrawCommandSequence`` pointer:
|
||
|
|
||
|
```c
|
||
|
static GDrawCommandSequence *s_command_seq;
|
||
|
```
|
||
|
|
||
|
Next, initialize the object in `init()` before calling `window_stack_push()`:
|
||
|
|
||
|
```nc|c
|
||
|
static void init() {
|
||
|
/* ... */
|
||
|
|
||
|
// Load the sequence
|
||
|
s_command_seq = gdraw_command_sequence_create_with_resource(RESOURCE_ID_CLOCK_SEQUENCE);
|
||
|
|
||
|
/* ... */
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Get the next frame and draw it in the ``LayerUpdateProc``. Then register a timer
|
||
|
to draw the next frame:
|
||
|
|
||
|
```c
|
||
|
// Milliseconds between frames
|
||
|
#define DELTA 13
|
||
|
|
||
|
static int s_index = 0;
|
||
|
|
||
|
/* ... */
|
||
|
|
||
|
static void next_frame_handler(void *context) {
|
||
|
// Draw the next frame
|
||
|
layer_mark_dirty(s_canvas_layer);
|
||
|
|
||
|
// Continue the sequence
|
||
|
app_timer_register(DELTA, next_frame_handler, NULL);
|
||
|
}
|
||
|
|
||
|
static void update_proc(Layer *layer, GContext *ctx) {
|
||
|
// Get the next frame
|
||
|
GDrawCommandFrame *frame = gdraw_command_sequence_get_frame_by_index(s_command_seq, s_index);
|
||
|
|
||
|
// If another frame was found, draw it
|
||
|
if (frame) {
|
||
|
gdraw_command_frame_draw(ctx, s_command_seq, frame, GPoint(0, 30));
|
||
|
}
|
||
|
|
||
|
// Advance to the next frame, wrapping if neccessary
|
||
|
int num_frames = gdraw_command_sequence_get_num_frames(s_command_seq);
|
||
|
s_index++;
|
||
|
if (s_index == num_frames) {
|
||
|
s_index = 0;
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Next, create a new ``Layer`` to utilize the ``LayerUpdateProc`` and add it to the
|
||
|
desired ``Window``.
|
||
|
|
||
|
Create the `Window` pointer:
|
||
|
|
||
|
```c
|
||
|
static Layer *s_canvas_layer;
|
||
|
```
|
||
|
|
||
|
Next, create the ``Layer`` and assign it to the new pointer. Set its update
|
||
|
procedure and add it to the ``Window``:
|
||
|
|
||
|
```c
|
||
|
static void main_window_load(Window *window) {
|
||
|
// Get Window information
|
||
|
Layer *window_layer = window_get_root_layer(window);
|
||
|
GRect bounds = layer_get_bounds(window_layer);
|
||
|
|
||
|
// Create the canvas Layer
|
||
|
s_canvas_layer = layer_create(GRect(30, 30, bounds.size.w, bounds.size.h));
|
||
|
|
||
|
// Set the LayerUpdateProc
|
||
|
layer_set_update_proc(s_canvas_layer, update_proc);
|
||
|
|
||
|
// Add to parent Window
|
||
|
layer_add_child(window_layer, s_canvas_layer);
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Start the animation loop using a timer at the end of initialization:
|
||
|
|
||
|
```c
|
||
|
// Start the animation
|
||
|
app_timer_register(DELTA, next_frame_handler, NULL);
|
||
|
```
|
||
|
|
||
|
Finally, remember to destroy the ``GDrawCommandSequence`` and ``Layer`` in
|
||
|
`main_window_unload()`:
|
||
|
|
||
|
```c
|
||
|
static void main_window_unload(Window *window) {
|
||
|
layer_destroy(s_canvas_layer);
|
||
|
gdraw_command_sequence_destroy(s_command_seq);
|
||
|
}
|
||
|
```
|
||
|
|
||
|
When run, the animation will be played by the timer at a framerate dictated by
|
||
|
`DELTA`, looking similar to the example shown below:
|
||
|
|
||
|

|
||
|
|
||
|
|
||
|
## What's Next?
|
||
|
|
||
|
You have now learned how to add vector images and animations to your apps.
|
||
|
Complete examples for these APIs are available under the `pebble-examples`
|
||
|
GitHub organization:
|
||
|
|
||
|
* [`pdc-image`]({{site.links.examples_org}}/pdc-image) - Example
|
||
|
implementation of a Pebble Draw Command Image.
|
||
|
|
||
|
* [`pdc-sequence`]({{site.links.examples_org}}/pdc-sequence) - Example
|
||
|
implementation of a Pebble Draw Command Sequence animated icon.
|
||
|
|
||
|
|
||
|
More advanced tutorials will be added here in the future, so keep checking back!
|