pebble/devsite/source/_posts/2015-12-02-Bitmap-Resources.md
2025-02-24 18:58:29 -08:00

7.2 KiB

title author tags
Unifying bitmap resources katharine
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 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:

{
	"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:

{
	"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.