pebble/devsite/source/_guides/app-resources/animated-images.md

146 lines
4.3 KiB
Markdown
Raw Permalink Normal View History

---
# 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.
title: Animated Images
description: |
How to add animated image resources to a project in the APNG format, and
display them in your app.
guide_group: app-resources
order: 0
platform_choice: true
---
The Pebble SDK allows animated images to be played inside an app using the
``GBitmapSequence`` API, which takes [APNG](https://en.wikipedia.org/wiki/APNG)
images as input files. APNG files are similar to well-known `.gif` files, which
are not supported directly but can be converted to APNG.
A similar effect can be achieved with multiple image resources, a
``BitmapLayer`` and an ``AppTimer``, but would require a lot more code. The
``GBitmapSequence`` API handles the reading, decompression, and frame
duration/count automatically.
## Converting GIF to APNG
A `.gif` file can be converted to the APNG `.png` format with
[gif2apng](http://gif2apng.sourceforge.net/) and the `-z0` flag:
```text
./gif2apng -z0 animation.gif
```
> Note: The file extension must be `.png`, **not** `.apng`.
## Adding an APNG
{% platform local %}
Include the APNG file in the `resources` array in `package.json` as a `raw`
resource:
```js
"resources": {
"media": [
{
"type":"raw",
"name":"ANIMATION",
"file":"images/animation.png"
}
]
}
```
{% endplatform %}
{% platform cloudpebble %}
To add the APNG file as a raw resource, click 'Add New' in the Resources section
of the sidebar, and set the 'Resource Type' as 'raw binary blob'.
{% endplatform %}
## Displaying APNG Frames
The ``GBitmapSequence`` will use a ``GBitmap`` as a container and update its
contents each time a new frame is read from the APNG file. This means that the
first step is to create a blank ``GBitmap`` to be this container.
Declare file-scope variables to hold the data:
```c
static GBitmapSequence *s_sequence;
static GBitmap *s_bitmap;
```
Load the APNG from resources into the ``GBitmapSequence`` variable, and use the
frame size to create the blank ``GBitmap`` frame container:
```c
// Create sequence
s_sequence = gbitmap_sequence_create_with_resource(RESOURCE_ID_ANIMATION);
// Create blank GBitmap using APNG frame size
GSize frame_size = gbitmap_sequence_get_bitmap_size(s_sequence);
s_bitmap = gbitmap_create_blank(frame_size, GBitmapFormat8Bit);
```
Once the app is ready to begin playing the animated image, advance each frame
using an ``AppTimer`` until the end of the sequence is reached. Loading the next
APNG frame is handled for you and written to the container ``GBitmap``.
Declare a ``BitmapLayer`` variable to display the current frame, and set it up
as described under
{% guide_link app-resources/images#displaying-an-image "Displaying An Image" %}.
```c
static BitmapLayer *s_bitmap_layer;
```
Create the callback to be used when the ``AppTimer`` has elapsed, and the next
frame should be displayed. This will occur in a loop until there are no more
frames, and ``gbitmap_sequence_update_bitmap_next_frame()`` returns `false`:
```c
static void timer_handler(void *context) {
uint32_t next_delay;
// Advance to the next APNG frame, and get the delay for this frame
if(gbitmap_sequence_update_bitmap_next_frame(s_sequence, s_bitmap, &next_delay)) {
// Set the new frame into the BitmapLayer
bitmap_layer_set_bitmap(s_bitmap_layer, s_bitmap);
layer_mark_dirty(bitmap_layer_get_layer(s_bitmap_layer));
// Timer for that frame's delay
app_timer_register(next_delay, timer_handler, NULL);
}
}
```
When appropriate, schedule the first frame advance with an ``AppTimer``:
```c
uint32_t first_delay_ms = 10;
// Schedule a timer to advance the first frame
app_timer_register(first_delay_ms, timer_handler, NULL);
```
When the app exits or the resource is no longer required, destroy the
``GBitmapSequence`` and the container ``GBitmap``:
```c
gbitmap_sequence_destroy(s_sequence);
gbitmap_destroy(s_bitmap);
```