pebble/devsite/source/_posts/2015-12-02-Bitmap-Resources.md

164 lines
7.2 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: Unifying bitmap resources
author: katharine
tags:
- Freshly Baked
---
With the upcoming release of firmware 3.8 on Pebble and Pebble Steel, and the
associated SDK 3.8, we have decided to redesign how image resources work in
Pebble apps.
Why change?
-----------
First, some history: when SDK 1 was originally released, and continuing up to
SDK 2.9, there was a single image type, `png`. A `png` resource would take a PNG
as input and spit out a custom, uncompressed, 1-bit-per-pixel image format we
called "pbi". This was the only bitmap format Pebble supported, and life was
simple.
With the release of SDK 3.0, we added firmware support for a new image format:
PNG (with some restrictions). This enabled Pebble to directly read compressed
images, and those images could be 1-bit, 2-bit, 4-bit or 8-bit palettised. The
existing `png` resource type was changed to produce these images instead of the
old PBI format, and everyone had smaller image resources.
Unfortunately, "png" isn't the best option for all cases. The old 1-bit format
supported some [compositing operations](``GCompOp``) that other image formats
do not support. We added the `pbi` format to achieve this legacy behavior.
Additionally, PNG decompression isn't free: loading a PNG resource requires
enough memory to hold the compressed image, the uncompressed image, and some
scratch space. This was often offset by the benefits of palettised images with
fewer bits per pixel, but sometimes it didn't fit. We added `pbi8`, which
produced 8-bit-per-pixel PBI images. This still left it impossible to generate
a palettized PBI, even though the format does exist.
As a further complication, since SDK 1 and continuing through the present day,
`pbi` (and `pbi8`) images have cropped transparent borders around the outside.
However, `png` images (as of SDK 3) do _not_ crop like this. The cropping
behavior was originally a bug, and is generally undesirable, but must be
maintained for backwards compatibility.
There is one additional exception to all of this: until SDK 3.8, the Aplite platform still
interprets `png` to mean `pbi`. It also interprets `pbi8` to mean `pbi`. When
we built the 3.8 SDK, we changed `png` to really mean `png` on Aplite.
Unfortunately, the more limited memory of the Aplite platform meant that these
PNGs sometimes did not have enough space to decompress. The only workaround was
to duplicate resources and use `targetPlatforms` to specify a `pbi` resource for
Aplite and a `png` resource for Basalt and Chalk.
The easiest answer was to keep `png` as an alias for `pbi` on Aplite—but then
there's no way of generating a real PNG for Aplite. Furthermore, the `png`,
`pbi` and `pbi8` trio was getting confusing, so we decided to do something else.
"bitmap" to the rescue
----------------------
As of SDK 3.8, `png`, `pbi` and `pbi8` **are all deprecated**. We are instead
introducing a new resource type, `bitmap`. This new resource type unifies all
the existing types, allows the SDK to use its best judgement, increases the
flexibility available to developers, and takes the guesswork out of advanced
image manipulation. It also removes the generally undesirable forced cropping
behavior.
The simplest option is to only specify that you want a `bitmap` resource, and
by default the SDK will do the most reasonable thing:
```js
{
"type": "bitmap",
"name": "IMAGE_BERRY_PUNCH",
"file": "images/berry-punch.png"
}
```
This will create an image with the smallest possible in-memory representation,
which will depend on the number of colors. On Aplite, where memory is tight,
it will optimize for low memory consumption by creating a pbi. On all other
platforms, where there is more memory to spare, it will create a png.
This behavior can be overridden using the following attributes:
* `memoryFormat`: This determines the `GBitmapFormat` that the resulting image
will have. The default value is `Smallest`, which picks the value that will
use the least memory. If you have specific requirements, you can specify:
`SmallestPalette`, `1BitPalette`, `2BitPalette`, `4BitPalette`, `1Bit` or
`8Bit`. If an image cannot be represented in the requested format, a build
error will result. This, for instance, enables palette manipulation with
static checking for confidence that you will have an appropriate palette to
manipulate.
* `spaceOptimization`: This determines whether we should optimize the image for
resource space (`storage`) or memory (`memory`). The default depends on the
memory available on the platform: Aplite defaults to `memory`, while other
platforms default to `storage`.
* `storageFormat`: This explicitly states whether an image should be stored on
flash as a PNG or pbi image. In most cases you should not specify this, and
instead depend on `spaceOptimization`. However, if you read resources
directly, this option exists to force a value.
So if you want an image that will always have a 2-bit palette and use as little
memory as possible, you can do this:
```js
{
"type": "bitmap",
"name": "IMAGE_TIME_TURNER",
"file": "images/time-turner.png",
"memoryFormat": "2BitPalette",
"spaceOptimization": "memory"
}
```
The resulting image will always have `GBitmapFormat2BitPalette`, even if it
could have a 1-bit palette. If it has more than four colors, the build will
fail.
In SDK 3.8-beta8, this would result in a 2-bit palettized pbi. However, this
is not guaranteed: we can change the format in the future, as long as the result
has ``GBitmapFormat2BitPalette`` and we prefer to optimise for memory
consumption where possible.
Finally, note that some combinations are impossible: for instance, a `1Bit`
image can never be stored as a PNG, so `"storageFormat": "png"` combined with
`"memoryFormat": "1Bit"` will be a compile error.
Migration
---------
If you have an existing app, how do you migrate it to use the new `bitmap` type?
If you were depending on the `pbi` cropping behavior, you will have to manually
crop your image. Beyond that, this table gives the equivalences:
| SDK 3.7 type | `bitmap` specification |
|--------------|----------------------------------------------|
| `png` | `{"type": "bitmap"}` |
| `pbi` | `{"type": "bitmap", "memoryFormat": "1Bit"}` |
| `pbi8` | `{"type": "bitmap", "storageFormat": "pbi"}` |
`png-trans` has been left out of this entire exercise. Its behavior is unchanged:
it will produce two pbis with `GBitmapFormat1Bit`. However, `png-trans` is also
deprecated and discouraged. As of SDK 3.8, all platforms support transparency
in images, and so should use `bitmap` instead.
If you have any questions, you can [find us on Discord]({{ site.links.discord_invite }})
or [contact us](/contact/).