mirror of
https://github.com/google/pebble.git
synced 2025-03-19 10:31:21 +00:00
164 lines
7.2 KiB
Markdown
164 lines
7.2 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.
|
||
|
|
||
|
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/).
|