mirror of
https://github.com/google/pebble.git
synced 2025-05-19 09:54:55 +00:00
Import the pebble dev site into devsite/
This commit is contained in:
parent
3b92768480
commit
527858cf4c
1359 changed files with 265431 additions and 0 deletions
466
devsite/source/tutorials/advanced/vector-animations.md
Normal file
466
devsite/source/tutorials/advanced/vector-animations.md
Normal file
|
@ -0,0 +1,466 @@
|
|||
---
|
||||
# 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!
|
Loading…
Add table
Add a link
Reference in a new issue