mirror of
https://github.com/google/pebble.git
synced 2025-05-06 18:01:41 -04:00
599 lines
19 KiB
C
599 lines
19 KiB
C
/*
|
|
* Copyright 2024 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.
|
|
*/
|
|
|
|
#include "gtypes.h"
|
|
#include "gbitmap_pbi.h"
|
|
#include "gbitmap_png.h"
|
|
|
|
#include "applib/applib_malloc.auto.h"
|
|
#include "applib/applib_resource_private.h"
|
|
#include "applib/graphics/graphics.h"
|
|
#include "process_state/app_state/app_state.h"
|
|
#include "system/logging.h"
|
|
#include "system/passert.h"
|
|
#include "syscall/syscall.h"
|
|
|
|
#include <string.h>
|
|
#include <stddef.h>
|
|
|
|
uint8_t gbitmap_get_bits_per_pixel(GBitmapFormat format) {
|
|
switch (format) {
|
|
case GBitmapFormat1Bit:
|
|
case GBitmapFormat1BitPalette:
|
|
return 1;
|
|
case GBitmapFormat2BitPalette:
|
|
return 2;
|
|
case GBitmapFormat4BitPalette:
|
|
return 4;
|
|
case GBitmapFormat8Bit:
|
|
case GBitmapFormat8BitCircular:
|
|
return 8;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//! @return the size in bytes of the palette for a given format
|
|
uint8_t gbitmap_get_palette_size(GBitmapFormat format) {
|
|
switch (format) {
|
|
case GBitmapFormat1Bit:
|
|
case GBitmapFormat8Bit:
|
|
case GBitmapFormat8BitCircular:
|
|
return 0;
|
|
default:
|
|
return (1 << gbitmap_get_bits_per_pixel(format));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uint16_t gbitmap_format_get_row_size_bytes(int16_t width, GBitmapFormat format) {
|
|
switch (format) {
|
|
case GBitmapFormat1Bit:
|
|
return ((width + 31) / 32 ) * 4; // word aligned bytes
|
|
case GBitmapFormat8Bit:
|
|
return width;
|
|
case GBitmapFormat1BitPalette:
|
|
case GBitmapFormat2BitPalette:
|
|
case GBitmapFormat4BitPalette:
|
|
return ((width * gbitmap_get_bits_per_pixel(format) + 7) / 8); // byte aligned
|
|
case GBitmapFormat8BitCircular:
|
|
return 0; // variable width
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static GBitmap* prv_allocate_gbitmap(void) {
|
|
if (process_manager_compiled_with_legacy2_sdk()) {
|
|
return (GBitmap *) applib_type_zalloc(GBitmapLegacy2);
|
|
}
|
|
return applib_type_zalloc(GBitmap);
|
|
}
|
|
|
|
static size_t prv_gbitmap_size(void) {
|
|
if (process_manager_compiled_with_legacy2_sdk()) {
|
|
return applib_type_size(GBitmapLegacy2);
|
|
}
|
|
return applib_type_size(GBitmap);
|
|
}
|
|
|
|
static void prv_init_gbitmap_version(GBitmap *bitmap) {
|
|
if (process_manager_compiled_with_legacy2_sdk()) {
|
|
bitmap->info.version = GBITMAP_VERSION_0;
|
|
}
|
|
bitmap->info.version = GBITMAP_VERSION_CURRENT;
|
|
}
|
|
|
|
uint8_t gbitmap_get_version(const GBitmap *bitmap) {
|
|
if (process_manager_compiled_with_legacy2_sdk()) {
|
|
return GBITMAP_VERSION_0;
|
|
}
|
|
return bitmap->info.version;
|
|
}
|
|
|
|
// indirection to allow conditional mocking in unit-tests
|
|
T_STATIC
|
|
#if !UNITTEST
|
|
// apparently, GCC doesn't inline this otherwise
|
|
// scary, I wonder how many more places like these aren't inlined
|
|
ALWAYS_INLINE
|
|
#endif
|
|
GBitmapDataRowInfo prv_gbitmap_get_data_row_info(const GBitmap *bitmap, uint16_t y) {
|
|
if (bitmap->info.format == GBitmapFormat8BitCircular) {
|
|
const GBitmapDataRowInfoInternal *info = &bitmap->data_row_infos[y];
|
|
return (GBitmapDataRowInfo) {
|
|
.data = (uint8_t *)bitmap->addr + info->offset,
|
|
.min_x = info->min_x,
|
|
.max_x = info->max_x,
|
|
};
|
|
} else {
|
|
return (GBitmapDataRowInfo) {
|
|
.data = (uint8_t*)bitmap->addr + y * bitmap->row_size_bytes,
|
|
.min_x = 0,
|
|
// while this is conceptually wrong for .max_x as it should be
|
|
// (.row_size_bytes / .bytes_per_pixel) - 1
|
|
// it's still a valid value as we assume grect_get_max_x(.bounds) < .row_size_bytes * bpp
|
|
// that way this is an efficient implementation of this functions contract
|
|
.max_x = grect_get_max_x(&bitmap->bounds) - 1,
|
|
};
|
|
}
|
|
}
|
|
|
|
MOCKABLE GBitmapDataRowInfo gbitmap_get_data_row_info(const GBitmap *bitmap, uint16_t y) {
|
|
return prv_gbitmap_get_data_row_info(bitmap, y);
|
|
}
|
|
|
|
void gbitmap_init_with_data(GBitmap *bitmap, const uint8_t *data) {
|
|
BitmapData* bitmap_data = (BitmapData*) data;
|
|
|
|
memset(bitmap, 0, prv_gbitmap_size());
|
|
|
|
bitmap->row_size_bytes = bitmap_data->row_size_bytes;
|
|
bitmap->info_flags = bitmap_data->info_flags;
|
|
// Force this to false, just in case someone passes us some funny looking data.
|
|
bitmap->info.is_bitmap_heap_allocated = false;
|
|
|
|
// Note that our container contains values for the origin, but we want to ignore them.
|
|
// This is because orginally we just serialized GBitmap to disk,
|
|
// but these fields don't really make sense for static images.
|
|
// These origin fields are only used when reusing a byte buffer in a sub bitmap.
|
|
// This allows us to have a shallow copy of a portion of a parent bitmap.
|
|
// See gbitmap_init_as_sub_bitmap.
|
|
bitmap->bounds.origin.x = 0; //((int16_t*)data)[2];
|
|
bitmap->bounds.origin.y = 0; //((int16_t*)data)[3];
|
|
|
|
bitmap->bounds.size.w = bitmap_data->width;
|
|
bitmap->bounds.size.h = bitmap_data->height;
|
|
|
|
bitmap->info.format = gbitmap_get_format(bitmap);
|
|
|
|
if (gbitmap_get_palette_size(gbitmap_get_format(bitmap)) > 0) {
|
|
PBL_ASSERTN(!process_manager_compiled_with_legacy2_sdk());
|
|
// Palette is positioned right after the pixel data
|
|
bitmap->palette = (GColor*)(bitmap_data->data +
|
|
(bitmap->row_size_bytes * bitmap->bounds.size.h));
|
|
// Don't flag this as heap allocated, as it gets freed along with pixel data
|
|
bitmap->info.is_palette_heap_allocated = false;
|
|
}
|
|
|
|
bitmap->addr = bitmap_data->data;
|
|
|
|
// Anything (not Legacy2) being loaded in this manner is being converted to the latest version.
|
|
prv_init_gbitmap_version(bitmap);
|
|
}
|
|
|
|
GBitmap* gbitmap_create_with_data(const uint8_t *data) {
|
|
GBitmap* bitmap = prv_allocate_gbitmap();
|
|
if (bitmap) {
|
|
gbitmap_init_with_data(bitmap, data);
|
|
}
|
|
return bitmap;
|
|
}
|
|
|
|
void gbitmap_init_as_sub_bitmap(GBitmap *sub_bitmap, const GBitmap *base_bitmap, GRect sub_rect) {
|
|
if (gbitmap_get_version(base_bitmap) == GBITMAP_VERSION_0) {
|
|
GBitmapLegacy2 *legacy_bitmap = (GBitmapLegacy2 *) sub_bitmap;
|
|
*legacy_bitmap = *(GBitmapLegacy2 *) base_bitmap;
|
|
// it's the responsibility of the parent bitmap to free the underlying data
|
|
legacy_bitmap->is_heap_allocated = false;
|
|
} else {
|
|
*sub_bitmap = *base_bitmap;
|
|
// it's the responsibility of the parent bitmap to free the underlying data and palette
|
|
sub_bitmap->info.is_palette_heap_allocated = false;
|
|
sub_bitmap->info.is_bitmap_heap_allocated = false;
|
|
}
|
|
grect_clip(&sub_rect, &base_bitmap->bounds);
|
|
sub_bitmap->bounds = sub_rect;
|
|
}
|
|
|
|
GBitmap* gbitmap_create_as_sub_bitmap(const GBitmap *base_bitmap, GRect sub_rect) {
|
|
GBitmap *bitmap = prv_allocate_gbitmap();
|
|
if (bitmap) {
|
|
gbitmap_init_as_sub_bitmap(bitmap, base_bitmap, sub_rect);
|
|
}
|
|
return bitmap;
|
|
}
|
|
|
|
static GColor* prv_allocate_palette(GBitmapFormat format) {
|
|
PBL_ASSERTN(!process_manager_compiled_with_legacy2_sdk());
|
|
GColor *palette = NULL;
|
|
uint8_t palette_size = gbitmap_get_palette_size(format);
|
|
if (palette_size > 0) {
|
|
palette = applib_zalloc(palette_size * sizeof(GColor));
|
|
}
|
|
return palette;
|
|
}
|
|
|
|
#define BITMAP_FORMAT_IS_CIRCULAR_FULL_SCREEN(size, format) \
|
|
((format) == GBitmapFormat8BitCircular && (size).w == DISP_COLS && (size).h == DISP_ROWS)
|
|
|
|
T_STATIC size_t prv_gbitmap_size_for_data(GSize size, GBitmapFormat format) {
|
|
#if PLATFORM_SPALDING
|
|
if (BITMAP_FORMAT_IS_CIRCULAR_FULL_SCREEN(size, format)) {
|
|
return DISPLAY_FRAMEBUFFER_BYTES;
|
|
}
|
|
#endif
|
|
return gbitmap_format_get_row_size_bytes(size.w, format) * size.h;
|
|
}
|
|
|
|
static bool prv_gbitmap_allocate_data_for_size(GBitmap *bitmap, GSize size, GBitmapFormat format) {
|
|
if (!bitmap) {
|
|
return false;
|
|
}
|
|
|
|
bitmap->row_size_bytes = gbitmap_format_get_row_size_bytes(size.w, format);
|
|
bitmap->bounds.size.w = size.w;
|
|
bitmap->bounds.size.h = size.h;
|
|
prv_init_gbitmap_version(bitmap);
|
|
bitmap->info.format = format;
|
|
|
|
const size_t data_size = prv_gbitmap_size_for_data(size, format);
|
|
bitmap->addr = applib_zalloc(data_size);
|
|
if (bitmap->addr) {
|
|
bitmap->info.is_bitmap_heap_allocated = true;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static GBitmap* prv_gbitmap_create_blank(GSize size, GBitmapFormat format) {
|
|
GBitmap *bitmap = prv_allocate_gbitmap();
|
|
if (bitmap) {
|
|
if (!prv_gbitmap_allocate_data_for_size(bitmap, size, format)) {
|
|
applib_free(bitmap);
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef PLATFORM_SPALDING
|
|
if (BITMAP_FORMAT_IS_CIRCULAR_FULL_SCREEN(size, format)) {
|
|
bitmap->data_row_infos = g_gbitmap_spalding_data_row_infos;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return bitmap;
|
|
}
|
|
|
|
static bool prv_platform_supports_format(GSize size, GBitmapFormat format) {
|
|
switch (format) {
|
|
#if PBL_BW
|
|
case GBitmapFormat1Bit:
|
|
case GBitmapFormat1BitPalette:
|
|
case GBitmapFormat2BitPalette:
|
|
return true;
|
|
#elif PBL_COLOR && PBL_RECT
|
|
case GBitmapFormat1Bit:
|
|
case GBitmapFormat8Bit:
|
|
case GBitmapFormat1BitPalette:
|
|
case GBitmapFormat2BitPalette:
|
|
case GBitmapFormat4BitPalette:
|
|
return true;
|
|
#elif PBL_COLOR && PBL_ROUND
|
|
case GBitmapFormat1Bit:
|
|
case GBitmapFormat8Bit:
|
|
case GBitmapFormat1BitPalette:
|
|
case GBitmapFormat2BitPalette:
|
|
case GBitmapFormat4BitPalette:
|
|
return true;
|
|
case GBitmapFormat8BitCircular:
|
|
return BITMAP_FORMAT_IS_CIRCULAR_FULL_SCREEN(size, format);
|
|
#endif
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool prv_is_palettized_format(GBitmapFormat format) {
|
|
return format >= GBitmapFormat1BitPalette && format <= GBitmapFormat4BitPalette;
|
|
}
|
|
|
|
T_STATIC GBitmap *prv_gbitmap_create_blank_internal_no_platform_checks(GSize size,
|
|
GBitmapFormat format) {
|
|
GBitmap* bitmap = prv_gbitmap_create_blank(size, format);
|
|
|
|
// If bitmap allocated and format requires a palette
|
|
if (bitmap && prv_is_palettized_format(format)) {
|
|
bitmap->palette = prv_allocate_palette(format);
|
|
if (bitmap->palette) {
|
|
bitmap->info.is_palette_heap_allocated = true;
|
|
} else {
|
|
gbitmap_destroy(bitmap);
|
|
bitmap = NULL;
|
|
}
|
|
}
|
|
|
|
return bitmap;
|
|
}
|
|
|
|
GBitmap* gbitmap_create_blank(GSize size, GBitmapFormat format) {
|
|
if (process_manager_compiled_with_legacy2_sdk() && format != GBitmapFormat1Bit) {
|
|
return NULL;
|
|
}
|
|
|
|
if (!prv_platform_supports_format(size, format)) {
|
|
return NULL;
|
|
}
|
|
|
|
return prv_gbitmap_create_blank_internal_no_platform_checks(size, format);
|
|
}
|
|
|
|
GBitmapLegacy2* gbitmap_create_blank_2bit(GSize size) {
|
|
return (GBitmapLegacy2 *) gbitmap_create_blank(size, GBitmapFormat1Bit);
|
|
}
|
|
|
|
GBitmap* gbitmap_create_blank_with_palette(GSize size, GBitmapFormat format,
|
|
GColor *palette, bool free_on_destroy) {
|
|
PBL_ASSERTN(!process_manager_compiled_with_legacy2_sdk());
|
|
|
|
if (!prv_platform_supports_format(size, format)) {
|
|
return NULL;
|
|
}
|
|
|
|
if (!prv_is_palettized_format(format)) {
|
|
return NULL;
|
|
}
|
|
|
|
GBitmap *bitmap = prv_gbitmap_create_blank(size, format);
|
|
if (bitmap) {
|
|
gbitmap_set_palette(bitmap, palette, free_on_destroy);
|
|
}
|
|
|
|
return bitmap;
|
|
}
|
|
|
|
// Adapted from http://aggregate.org/MAGIC/#Bit%20Reversal
|
|
T_STATIC uint8_t prv_byte_reverse(uint8_t b) {
|
|
b = (b & 0xaa) >> 1 | (b & 0x55) << 1;
|
|
b = (b & 0xcc) >> 2 | (b & 0x33) << 2;
|
|
b = (b & 0xf0) >> 4 | (b & 0x0f) << 4;
|
|
return b;
|
|
}
|
|
|
|
GBitmap* gbitmap_create_palettized_from_1bit(const GBitmap *src_bitmap) {
|
|
PBL_ASSERTN(!process_manager_compiled_with_legacy2_sdk());
|
|
GBitmap *bitmap = NULL;
|
|
if (src_bitmap && gbitmap_get_format(src_bitmap) == GBitmapFormat1Bit) {
|
|
// Allocate the full size of the image up until the end of the bounds.
|
|
// This eliminates edge cases where the bounds may start within a byte,
|
|
// and not enough space would be allocated. This allows us to do all copying
|
|
// from { 0, 0 } and simplifies copy.
|
|
GSize size = (GSize) {
|
|
.w = src_bitmap->bounds.size.w + src_bitmap->bounds.origin.x,
|
|
.h = src_bitmap->bounds.size.h + src_bitmap->bounds.origin.y
|
|
};
|
|
bitmap = gbitmap_create_blank(size, GBitmapFormat1BitPalette);
|
|
if (bitmap) {
|
|
// Perform conversion
|
|
uint8_t *src_data = (uint8_t *)src_bitmap->addr;
|
|
uint8_t *dest_data = (uint8_t *)bitmap->addr;
|
|
for (int y = 0; y < bitmap->bounds.size.h; ++y) {
|
|
for (int b = 0; b < bitmap->row_size_bytes; ++b) {
|
|
int dest_idx = y * bitmap->row_size_bytes + b;
|
|
int src_idx = y * src_bitmap->row_size_bytes + b;
|
|
dest_data[dest_idx] = prv_byte_reverse(src_data[src_idx]);
|
|
}
|
|
}
|
|
bitmap->bounds = src_bitmap->bounds;
|
|
bitmap->palette[0] = GColorBlack;
|
|
bitmap->palette[1] = GColorWhite;
|
|
}
|
|
}
|
|
return bitmap;
|
|
}
|
|
|
|
bool gbitmap_init_with_resource(GBitmap* bitmap, uint32_t resource_id) {
|
|
ResAppNum app_resource_bank = sys_get_current_resource_num();
|
|
return gbitmap_init_with_resource_system(bitmap, app_resource_bank, resource_id);
|
|
}
|
|
|
|
GBitmap *gbitmap_create_with_resource(uint32_t resource_id) {
|
|
ResAppNum app_num = sys_get_current_resource_num();
|
|
return gbitmap_create_with_resource_system(app_num, resource_id);
|
|
}
|
|
|
|
GBitmap *gbitmap_create_with_resource_system(ResAppNum app_num, uint32_t resource_id) {
|
|
GBitmap *bitmap = prv_allocate_gbitmap();
|
|
if (!bitmap) {
|
|
return NULL;
|
|
}
|
|
|
|
if (!gbitmap_init_with_resource_system(bitmap, app_num, resource_id)) {
|
|
applib_free(bitmap);
|
|
return NULL;
|
|
}
|
|
|
|
return bitmap;
|
|
}
|
|
|
|
static bool prv_init_with_pbi_data(GBitmap *bitmap, uint8_t *data, size_t data_size,
|
|
bool is_builtin) {
|
|
// Initialize our metadata
|
|
gbitmap_init_with_data(bitmap, data);
|
|
if (is_builtin) {
|
|
// for builtin resources, we don't do the extra bitmap manipulation below.
|
|
return true;
|
|
}
|
|
|
|
// Verify the metadata is valid
|
|
const GBitmapFormat format = gbitmap_get_format(bitmap);
|
|
const size_t addr_offset = offsetof(BitmapData, data);
|
|
const uint32_t pixel_data_bytes = bitmap->row_size_bytes * bitmap->bounds.size.h;
|
|
const uint32_t required_total_size_bytes =
|
|
addr_offset + // header size
|
|
pixel_data_bytes + // pixel data
|
|
gbitmap_get_palette_size(format); // palette data
|
|
|
|
const uint32_t required_row_size_bits =
|
|
(bitmap->bounds.size.w * gbitmap_get_bits_per_pixel(format));
|
|
// Convert from 8 bits in a byte, taking care to round up to the next whole byte.
|
|
const uint32_t required_row_size_bytes = (required_row_size_bits + 7) / 8;
|
|
|
|
if (data_size != required_total_size_bytes ||
|
|
required_row_size_bytes > bitmap->row_size_bytes) {
|
|
PBL_LOG(LOG_LEVEL_WARNING, "Bitmap metadata is inconsistent! data_size %u",
|
|
(unsigned int) data_size);
|
|
PBL_LOG(LOG_LEVEL_WARNING, "format %u row_size_bytes %"PRIu16" width %"PRId16" height %"PRId16,
|
|
format, bitmap->row_size_bytes, bitmap->bounds.size.w, bitmap->bounds.size.h);
|
|
return false;
|
|
}
|
|
|
|
|
|
// Move the actual pixel data up to the front of the buffer.
|
|
// This way bitmap->addr points to the start of the buffer and can be directly freed.
|
|
memmove(data, data + addr_offset, data_size - addr_offset);
|
|
bitmap->addr = data;
|
|
bitmap->info.is_bitmap_heap_allocated = true;
|
|
|
|
// Move where the palette now points to, palette is positioned right after the pixel data
|
|
if (gbitmap_get_palette_size(format) > 0) {
|
|
bitmap->palette = (GColor*)((uint8_t*)bitmap->addr +
|
|
(bitmap->row_size_bytes * bitmap->bounds.size.h));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool gbitmap_init_with_resource_system(GBitmap* bitmap, ResAppNum app_num, uint32_t resource_id) {
|
|
if (!bitmap) {
|
|
return false;
|
|
}
|
|
|
|
memset(bitmap, 0, prv_gbitmap_size());
|
|
|
|
const size_t data_size = sys_resource_size(app_num, resource_id);
|
|
uint8_t *data = applib_resource_mmap_or_load(app_num, resource_id, 0, data_size, false);
|
|
if (!data) {
|
|
return false;
|
|
}
|
|
|
|
// Scan the resource data to see if it contains PNG data
|
|
if (gbitmap_png_data_is_png(data, data_size)) {
|
|
const bool result = gbitmap_init_with_png_data(bitmap, data, data_size);
|
|
// the actual pixels live uncompressed on the heap now, we can free the PNG data
|
|
applib_resource_munmap_or_free(data);
|
|
return result;
|
|
}
|
|
|
|
const bool mmapped = applib_resource_is_mmapped(data);
|
|
if (prv_init_with_pbi_data(bitmap, data, data_size, mmapped)) {
|
|
// in order to make memory-mapped bitmaps work, we need to decrement the reference counter
|
|
// when we destroy it. This case is different from a sub-bitmap that shares the bitmap
|
|
// data. We use .is_bitmap_heap_allocated=true here so that bitmap_deinit() can take care
|
|
// of it.
|
|
// As the pixel data is either memory-mapped or heap-allocated we always say "true"
|
|
bitmap->info.is_bitmap_heap_allocated = true;
|
|
return true;
|
|
} else {
|
|
applib_resource_munmap_or_free(data);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
uint16_t gbitmap_get_bytes_per_row(const GBitmap *bitmap) {
|
|
if (!bitmap) {
|
|
return 0;
|
|
}
|
|
return bitmap->row_size_bytes;
|
|
}
|
|
|
|
static bool prv_gbitmap_is_context(const GBitmap *bitmap) {
|
|
return (bitmap->addr == graphics_context_get_bitmap(app_state_get_graphics_context())->addr);
|
|
}
|
|
|
|
GBitmapFormat gbitmap_get_format(const GBitmap *bitmap) {
|
|
if (!bitmap) {
|
|
return GBitmapFormat1Bit;
|
|
}
|
|
|
|
if (process_manager_compiled_with_legacy2_sdk() ||
|
|
gbitmap_get_version(bitmap) == GBITMAP_VERSION_0) {
|
|
// If the bitmap is from the graphics context, return its format
|
|
// otherwise return the Legacy2 default 1-Bit format
|
|
// to support legacy applications that mis-set the format flags
|
|
return (prv_gbitmap_is_context(bitmap)) ? bitmap->info.format : GBitmapFormat1Bit;
|
|
}
|
|
return bitmap->info.format;
|
|
}
|
|
|
|
uint8_t* gbitmap_get_data(const GBitmap *bitmap) {
|
|
if (!bitmap) {
|
|
return NULL;
|
|
}
|
|
return bitmap->addr;
|
|
}
|
|
|
|
void gbitmap_set_data(GBitmap *bitmap, uint8_t *data, GBitmapFormat format,
|
|
uint16_t row_size_bytes, bool free_on_destroy) {
|
|
if (bitmap) {
|
|
bitmap->addr = data;
|
|
bitmap->info.format = format;
|
|
bitmap->row_size_bytes = row_size_bytes;
|
|
bitmap->info.is_bitmap_heap_allocated = free_on_destroy;
|
|
}
|
|
}
|
|
|
|
GColor* gbitmap_get_palette(const GBitmap *bitmap) {
|
|
PBL_ASSERTN(!process_manager_compiled_with_legacy2_sdk());
|
|
if (!bitmap) {
|
|
return NULL;
|
|
}
|
|
return bitmap->palette;
|
|
}
|
|
|
|
void gbitmap_set_palette(GBitmap *bitmap, GColor *palette, bool free_on_destroy) {
|
|
PBL_ASSERTN(!process_manager_compiled_with_legacy2_sdk());
|
|
if (bitmap && palette) {
|
|
if (gbitmap_get_info(bitmap).is_palette_heap_allocated) {
|
|
applib_free(bitmap->palette);
|
|
}
|
|
bitmap->palette = palette;
|
|
bitmap->info.is_palette_heap_allocated = free_on_destroy;
|
|
}
|
|
}
|
|
|
|
GRect gbitmap_get_bounds(const GBitmap *bitmap) {
|
|
if (!bitmap) {
|
|
return GRectZero;
|
|
}
|
|
return bitmap->bounds;
|
|
}
|
|
|
|
void gbitmap_set_bounds(GBitmap *bitmap, GRect bounds) {
|
|
if (bitmap) {
|
|
bitmap->bounds = bounds;
|
|
}
|
|
}
|
|
|
|
void gbitmap_deinit(GBitmap* bitmap) {
|
|
if (gbitmap_get_info(bitmap).is_bitmap_heap_allocated) {
|
|
applib_resource_munmap_or_free(bitmap->addr);
|
|
}
|
|
bitmap->addr = NULL;
|
|
|
|
if (!process_manager_compiled_with_legacy2_sdk()) {
|
|
if (gbitmap_get_info(bitmap).is_palette_heap_allocated) {
|
|
applib_free(bitmap->palette);
|
|
}
|
|
bitmap->palette = NULL;
|
|
}
|
|
}
|
|
|
|
void gbitmap_destroy(GBitmap* bitmap) {
|
|
if (!bitmap) {
|
|
return;
|
|
}
|
|
gbitmap_deinit(bitmap);
|
|
applib_free(bitmap);
|
|
}
|