Kaynağa Gözat

Sprite: new format

SG 1 yıl önce
ebeveyn
işleme
26c5e2c556
3 değiştirilmiş dosya ile 107 ekleme ve 112 silme
  1. 77 0
      scripts/sprite_builder.py
  2. 27 101
      sprite.c
  3. 3 11
      sprite.h

+ 77 - 0
scripts/sprite_builder.py

@@ -0,0 +1,77 @@
+#!/usr/bin/env python3
+import argparse
+import io
+import logging
+import os
+import struct
+
+from PIL import Image, ImageOps
+
+# XBM flipper sprite (.fxbm) is 1-bit depth, width-padded to 8 bits
+# file format:
+#   uint32 size of the rest of the file in bytes
+#   uint32 width in px
+#   uint32 height in px
+#   uint8[] pixel data, every row is padded to 8 bits (like XBM)
+
+def image2xbm(input_file_path):
+    with Image.open(input_file_path) as im:
+        with io.BytesIO() as output:
+            bw = im.convert("1")
+            bw = ImageOps.invert(bw)
+            bw.save(output, format="XBM")
+            return output.getvalue()
+
+def xbm2fxbm(data):
+    # hell as it is, but it works
+    f = io.StringIO(data.decode().strip())
+    width = int(f.readline().strip().split(" ")[2])
+    height = int(f.readline().strip().split(" ")[2])
+    data = f.read().strip().replace("\n", "").replace(" ", "").split("=")[1][:-1]
+    data_str = data[1:-1].replace(",", " ").replace("0x", "")
+    image_bin = bytearray.fromhex(data_str)
+
+    output = struct.pack("<I", len(image_bin) + 8)
+    output += struct.pack("<II", width, height)
+    output += image_bin
+    return output
+
+def process_sprites(input_dir, output_dir):
+    for root, dirs, files in os.walk(input_dir):
+        for file in files:
+            input_file_path = os.path.join(root, file)
+            rel_path = os.path.relpath(input_file_path, input_dir)
+            new_rel_path = os.path.splitext(rel_path)[0] + ".fxbm"
+            output_file_path = os.path.join(output_dir, new_rel_path)
+
+            os.makedirs(os.path.dirname(output_file_path), exist_ok=True)
+
+            print(f"Converting '{rel_path}' to '{new_rel_path}'")
+            with open(output_file_path, "wb") as f:
+                xbm = image2xbm(input_file_path)
+                f.write(xbm2fxbm(xbm))
+
+def clear_directory(directory):
+    for root, dirs, files in os.walk(directory, topdown=False):
+        for name in files:
+            os.remove(os.path.join(root, name))
+        for name in dirs:
+            os.rmdir(os.path.join(root, name))
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument("source", help="Source directory")
+    parser.add_argument("target", help="Target directory")
+    args = parser.parse_args()
+
+    logging.basicConfig(level=logging.ERROR)
+    logger = logging.getLogger(__name__)
+    logger.debug(f"Building sprites from {args.source} to {args.target}")
+
+    clear_directory(args.target)
+    process_sprites(args.source, args.target)
+
+    return 0
+
+if __name__ == "__main__":
+    main()

+ 27 - 101
sprite.c

@@ -1,129 +1,55 @@
 #include "sprite.h"
+#include <storage/storage.h>
+
+#ifdef SPRITE_DEBUG
+#define SPRITE_D(...) FURI_LOG_D("Sprite", __VA_ARGS__)
+#define SPRITE_E(...) FURI_LOG_E("Sprite", __VA_ARGS__)
+#else
+#define SPRITE_D(...)
+#define SPRITE_E(...)
+#endif
 
 struct Sprite {
-    uint8_t width;
-    uint8_t height;
-    uint8_t width_in_bytes;
-    uint8_t* data;
+    uint32_t width;
+    uint32_t height;
+    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;
+Sprite* sprite_alloc(const char* path) {
+    Storage* storage = furi_record_open(RECORD_STORAGE);
     File* file = storage_file_alloc(storage);
+    Sprite* sprite = NULL;
 
     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");
+        if(!storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING)) {
+            SPRITE_E("Failed to open file: %s", path);
             break;
         }
 
-        // check if it's a bmp file
-        if(header[0] != 'B' || header[1] != 'M') {
-            FURI_LOG_E(TAG, "Invalid BMP header");
+        uint32_t size = 0;
+        if(storage_file_read(file, &size, sizeof(size)) != sizeof(size)) {
+            SPRITE_E("Failed to read file size: %s", path);
             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);
+        sprite = malloc(size);
+        if(storage_file_read(file, sprite, size) != size) {
+            SPRITE_E("Failed to read file: %s", path);
+            free(sprite);
+            sprite = NULL;
             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;
+        SPRITE_D(
+            "Loaded sprite: %s, width: %lu, height: %lu", path, sprite->width, sprite->height);
     } 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);
 }
 

+ 3 - 11
sprite.h

@@ -1,6 +1,6 @@
 #pragma once
 #include <stdbool.h>
-#include <storage/storage.h>
+#include <stddef.h>
 #include <gui/canvas.h>
 
 #ifdef __cplusplus
@@ -10,17 +10,9 @@ extern "C" {
 typedef struct Sprite Sprite;
 
 /** Sprite allocator
- * @return Sprite*  Sprite instance
+ * @return Sprite*  Sprite instance or NULL, if failed
  */
-Sprite* sprite_alloc();
-
-/** Load sprite from bmp file
- * @param sprite Sprite instance
- * @param storage Storage instance
- * @param bmp_path path to bmp file
- * @return bool true if success
- */
-bool sprite_load_from_bmp(Sprite* sprite, Storage* storage, const char* bmp_path);
+Sprite* sprite_alloc(const char* path);
 
 /** Sprite deallocator
  * @param sprite Sprite instance