|
|
@@ -0,0 +1,140 @@
|
|
|
+#include "sprite.h"
|
|
|
+
|
|
|
+struct Sprite {
|
|
|
+ uint8_t width;
|
|
|
+ uint8_t height;
|
|
|
+ uint8_t width_in_bytes;
|
|
|
+ uint8_t* data;
|
|
|
+};
|
|
|
+
|
|
|
+#define TAG "Sprite"
|
|
|
+
|
|
|
+static void sprite_set_pixel(Sprite* sprite, uint8_t x, uint8_t y, bool color) {
|
|
|
+ size_t p = y * sprite->width_in_bytes + x / 8;
|
|
|
+ size_t b = x % 8;
|
|
|
+
|
|
|
+ if(color) {
|
|
|
+ sprite->data[p] |= 1 << b;
|
|
|
+ } else {
|
|
|
+ sprite->data[p] &= ~(1 << b);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static bool sprite_load(Storage* storage, const char* bmp_path, Sprite* sprite) {
|
|
|
+ bool result = false;
|
|
|
+ File* file = storage_file_alloc(storage);
|
|
|
+
|
|
|
+ do {
|
|
|
+ if(!storage_file_open(file, bmp_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
|
|
+ FURI_LOG_E(TAG, "Failed to open file: %s", bmp_path);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // actually header is 54 bytes, but we only need first 38
|
|
|
+ uint8_t header[38];
|
|
|
+
|
|
|
+ if(!storage_file_read(file, header, sizeof(header))) {
|
|
|
+ FURI_LOG_E(TAG, "Failed to read file header");
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // check if it's a bmp file
|
|
|
+ if(header[0] != 'B' || header[1] != 'M') {
|
|
|
+ FURI_LOG_E(TAG, "Invalid BMP header");
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // load bmp info
|
|
|
+ const uint32_t data_offset = *(uint32_t*)&header[10];
|
|
|
+ const uint32_t width = *(uint32_t*)&header[18];
|
|
|
+ const uint32_t height = *(uint32_t*)&header[22];
|
|
|
+ const uint16_t bpp = *(uint16_t*)&header[28];
|
|
|
+ const uint32_t size = *(uint32_t*)&header[34];
|
|
|
+
|
|
|
+ uint8_t* data = malloc(size);
|
|
|
+ storage_file_seek(file, data_offset, true);
|
|
|
+ if(!storage_file_read(file, data, size)) {
|
|
|
+ FURI_LOG_E(TAG, "Failed to read file data");
|
|
|
+ free(data);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ sprite->width = width;
|
|
|
+ sprite->height = height;
|
|
|
+ sprite->width_in_bytes = width / 8 + (width % 8 ? 1 : 0);
|
|
|
+
|
|
|
+ sprite->data = malloc(sprite->width_in_bytes * sprite->height);
|
|
|
+
|
|
|
+ // convert bmp data to 1-bit xbm
|
|
|
+ size_t p = 0;
|
|
|
+ size_t x = 0;
|
|
|
+ size_t y = 0;
|
|
|
+ const size_t bpp_in_bytes = bpp / 8;
|
|
|
+
|
|
|
+ while(p < size) {
|
|
|
+ // sum all bytes in pixel
|
|
|
+ uint32_t bmp_px = 0;
|
|
|
+ for(size_t i = 0; i < bpp_in_bytes; i++) {
|
|
|
+ bmp_px += data[p + i];
|
|
|
+ }
|
|
|
+ p += bpp_in_bytes;
|
|
|
+
|
|
|
+ // if sum divided by bytes per pixel is less than 128, it's a black pixel
|
|
|
+ bool color = bmp_px / (bpp_in_bytes) < 128;
|
|
|
+
|
|
|
+ // set pixel
|
|
|
+ sprite_set_pixel(sprite, x, height - y - 1, color);
|
|
|
+
|
|
|
+ // advance to next pixel
|
|
|
+ x++;
|
|
|
+
|
|
|
+ // advance to next row
|
|
|
+ if(x >= width) {
|
|
|
+ // consider that bmp data is padded to 4 bytes
|
|
|
+ size_t row_reminder = p % 4;
|
|
|
+ if(row_reminder) {
|
|
|
+ p += 4 - row_reminder;
|
|
|
+ }
|
|
|
+
|
|
|
+ x = 0;
|
|
|
+ y++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ free(data);
|
|
|
+
|
|
|
+ result = true;
|
|
|
+ } while(false);
|
|
|
+
|
|
|
+ storage_file_free(file);
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
+Sprite* sprite_alloc() {
|
|
|
+ Sprite* sprite = malloc(sizeof(Sprite));
|
|
|
+ sprite->width = 0;
|
|
|
+ sprite->height = 0;
|
|
|
+ sprite->data = NULL;
|
|
|
+ return sprite;
|
|
|
+}
|
|
|
+
|
|
|
+bool sprite_load_from_bmp(Sprite* sprite, Storage* storage, const char* bmp_path) {
|
|
|
+ return sprite_load(storage, bmp_path, sprite);
|
|
|
+}
|
|
|
+
|
|
|
+void sprite_free(Sprite* sprite) {
|
|
|
+ free(sprite->data);
|
|
|
+ free(sprite);
|
|
|
+}
|
|
|
+
|
|
|
+size_t sprite_get_width(Sprite* sprite) {
|
|
|
+ return sprite->width;
|
|
|
+}
|
|
|
+
|
|
|
+size_t sprite_get_height(Sprite* sprite) {
|
|
|
+ return sprite->height;
|
|
|
+}
|
|
|
+
|
|
|
+void canvas_draw_sprite(Canvas* canvas, Sprite* sprite, int32_t x, int32_t y) {
|
|
|
+ canvas_draw_xbm(canvas, x, y, sprite->width, sprite->height, sprite->data);
|
|
|
+}
|