ghostbt 0.2.0__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. {ghostbt-0.2.0 → ghostbt-0.2.2}/PKG-INFO +18 -4
  2. {ghostbt-0.2.0 → ghostbt-0.2.2}/README.md +17 -3
  3. ghostbt-0.2.2/ghostbt/__init__.py +1 -0
  4. {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/asset.py +54 -2
  5. {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/cli.py +1 -1
  6. {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/icon.py +68 -0
  7. {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt.egg-info/PKG-INFO +18 -4
  8. {ghostbt-0.2.0 → ghostbt-0.2.2}/pyproject.toml +1 -1
  9. ghostbt-0.2.0/ghostbt/__init__.py +0 -1
  10. {ghostbt-0.2.0 → ghostbt-0.2.2}/LICENSE +0 -0
  11. {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/__main__.py +0 -0
  12. {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/build.py +0 -0
  13. {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/config.py +0 -0
  14. {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/create.py +0 -0
  15. {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/data/sdk/ghostesp_plugin_api.h +0 -0
  16. {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/data/templates/basic_app/CMakeLists.txt +0 -0
  17. {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/data/templates/basic_app/main/CMakeLists.txt +0 -0
  18. {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/data/templates/basic_app/main/idf_component.yml +0 -0
  19. {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/data/templates/basic_app/main/{{APP_SYMBOL}}.c +0 -0
  20. {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/data/templates/basic_app/manifest.json +0 -0
  21. {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/data/templates/basic_app/sdkconfig.defaults +0 -0
  22. {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/dist.py +0 -0
  23. {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/esp_idf.py +0 -0
  24. {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/firmware.py +0 -0
  25. {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/flash.py +0 -0
  26. {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/package.py +0 -0
  27. {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/utils.py +0 -0
  28. {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt.egg-info/SOURCES.txt +0 -0
  29. {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt.egg-info/dependency_links.txt +0 -0
  30. {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt.egg-info/entry_points.txt +0 -0
  31. {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt.egg-info/requires.txt +0 -0
  32. {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt.egg-info/top_level.txt +0 -0
  33. {ghostbt-0.2.0 → ghostbt-0.2.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ghostbt
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Ghost Build Tool - build and package GhostESP native SD apps
5
5
  License-Expression: GPL-3.0
6
6
  Project-URL: Homepage, https://github.com/GhostESP-Revival/GhostESP
@@ -131,17 +131,31 @@ When loading a `.gtheme`, firmware streams entries into `/mnt/ghostesp/themes/ac
131
131
 
132
132
  ### PSRAM vs internal RAM
133
133
 
134
- **PSRAM devices:** Up to 32 icons cached in PSRAM, tiled or scaled backgrounds supported. Background images smaller than the screen tile automatically; larger images scale to fill using LVGL zoom. A 128x128 background will scale up to fill any screen. Background images are always loaded into PSRAM.
134
+ **PSRAM devices:** Up to 32 icons cached in PSRAM, tiled or scaled backgrounds supported. Background images smaller than the screen tile automatically; larger images scale to fill using LVGL zoom. A 128x128 background will scale up to fill any screen. Background images are always loaded into PSRAM. Any image format works; deflate-compressed payloads decode into PSRAM.
135
135
 
136
- **Internal-only devices:** 2 icons cached in internal RAM, no background images. Icons fall back to compiled-in artwork when cache is full. The pack still loads colors and icon mappings; only the image cache is limited.
136
+ **Internal-only devices:** 2 icons cached in internal RAM. Background tile (≤32x32) supported — LVGL tiles the small image across the screen. Icon size capped at 32x32 RGB565A8; deflate-compressed payloads rejected. Use the `indexed_4bpp` format for icons and the background tile to fit in internal RAM: a 32x32 indexed icon is 576 bytes vs 3,072 bytes for RGB565A8 (~5x smaller). Icons fall back to compiled-in artwork when the cache is full. The pack still loads colors and icon mappings; only the image cache is limited.
137
+
138
+ ### Image formats
139
+
140
+ | Format | Code | Size at 32x32 | Notes |
141
+ |--------|------|---------------|-------|
142
+ | `rgb565` | 1 | 2,048 B | No alpha. Best for opaque backgrounds. |
143
+ | `rgb565a8` | 2 | 3,072 B | RGB565 + separate alpha plane. Default for icons. |
144
+ | `indexed_4bpp` | 3 | 576 B | 16-color palette, packed 4-bit pixels. Ideal for internal-RAM devices; the `gbt` tool quantizes the source PNG at build time. |
145
+
146
+ Set the pack-wide icon format with `icon_format` in the source manifest, or override per-background via the `format` field on each `background_sources` entry. Indexed payloads are always stored uncompressed.
137
147
 
138
148
  Convert one image directly:
139
149
 
140
150
  ```bash
151
+ # Default — RGB565A8, 3 KB for 32x32
141
152
  gbt asset image ./wifi.png --out ./wifi_l.gimg --width 64 --height 64 --format rgb565a8
153
+
154
+ # Internal-RAM friendly — indexed 4bpp, 576 B for 32x32
155
+ gbt asset image ./wifi.png --out ./wifi_l.gimg --width 64 --height 64 --format indexed_4bpp
142
156
  ```
143
157
 
144
- The generated pack folder contains `manifest.json`, `checksums.json`, and generated `.gimg` files. Asset packs store raw `.gimg` payloads by default so firmware does not run deflate decompression from small UI task stacks. Missing icons are fine; firmware falls back to built-in artwork for any icon not present in the selected pack. Background tiles are loaded from PSRAM only.
158
+ The generated pack folder contains `manifest.json`, `checksums.json`, and generated `.gimg` files. Asset packs store raw `.gimg` payloads by default so firmware does not run deflate decompression from small UI task stacks. Missing icons are fine; firmware falls back to built-in artwork for any icon not present in the selected pack. Full-screen background images are loaded into PSRAM only; the smaller tiled background path works on internal-RAM devices at ≤32x32.
145
159
 
146
160
  ## Requirements
147
161
 
@@ -105,17 +105,31 @@ When loading a `.gtheme`, firmware streams entries into `/mnt/ghostesp/themes/ac
105
105
 
106
106
  ### PSRAM vs internal RAM
107
107
 
108
- **PSRAM devices:** Up to 32 icons cached in PSRAM, tiled or scaled backgrounds supported. Background images smaller than the screen tile automatically; larger images scale to fill using LVGL zoom. A 128x128 background will scale up to fill any screen. Background images are always loaded into PSRAM.
108
+ **PSRAM devices:** Up to 32 icons cached in PSRAM, tiled or scaled backgrounds supported. Background images smaller than the screen tile automatically; larger images scale to fill using LVGL zoom. A 128x128 background will scale up to fill any screen. Background images are always loaded into PSRAM. Any image format works; deflate-compressed payloads decode into PSRAM.
109
109
 
110
- **Internal-only devices:** 2 icons cached in internal RAM, no background images. Icons fall back to compiled-in artwork when cache is full. The pack still loads colors and icon mappings; only the image cache is limited.
110
+ **Internal-only devices:** 2 icons cached in internal RAM. Background tile (≤32x32) supported — LVGL tiles the small image across the screen. Icon size capped at 32x32 RGB565A8; deflate-compressed payloads rejected. Use the `indexed_4bpp` format for icons and the background tile to fit in internal RAM: a 32x32 indexed icon is 576 bytes vs 3,072 bytes for RGB565A8 (~5x smaller). Icons fall back to compiled-in artwork when the cache is full. The pack still loads colors and icon mappings; only the image cache is limited.
111
+
112
+ ### Image formats
113
+
114
+ | Format | Code | Size at 32x32 | Notes |
115
+ |--------|------|---------------|-------|
116
+ | `rgb565` | 1 | 2,048 B | No alpha. Best for opaque backgrounds. |
117
+ | `rgb565a8` | 2 | 3,072 B | RGB565 + separate alpha plane. Default for icons. |
118
+ | `indexed_4bpp` | 3 | 576 B | 16-color palette, packed 4-bit pixels. Ideal for internal-RAM devices; the `gbt` tool quantizes the source PNG at build time. |
119
+
120
+ Set the pack-wide icon format with `icon_format` in the source manifest, or override per-background via the `format` field on each `background_sources` entry. Indexed payloads are always stored uncompressed.
111
121
 
112
122
  Convert one image directly:
113
123
 
114
124
  ```bash
125
+ # Default — RGB565A8, 3 KB for 32x32
115
126
  gbt asset image ./wifi.png --out ./wifi_l.gimg --width 64 --height 64 --format rgb565a8
127
+
128
+ # Internal-RAM friendly — indexed 4bpp, 576 B for 32x32
129
+ gbt asset image ./wifi.png --out ./wifi_l.gimg --width 64 --height 64 --format indexed_4bpp
116
130
  ```
117
131
 
118
- The generated pack folder contains `manifest.json`, `checksums.json`, and generated `.gimg` files. Asset packs store raw `.gimg` payloads by default so firmware does not run deflate decompression from small UI task stacks. Missing icons are fine; firmware falls back to built-in artwork for any icon not present in the selected pack. Background tiles are loaded from PSRAM only.
132
+ The generated pack folder contains `manifest.json`, `checksums.json`, and generated `.gimg` files. Asset packs store raw `.gimg` payloads by default so firmware does not run deflate decompression from small UI task stacks. Missing icons are fine; firmware falls back to built-in artwork for any icon not present in the selected pack. Full-screen background images are loaded into PSRAM only; the smaller tiled background path works on internal-RAM devices at ≤32x32.
119
133
 
120
134
  ## Requirements
121
135
 
@@ -0,0 +1 @@
1
+ __version__ = "0.2.2"
@@ -4,7 +4,7 @@ import shutil
4
4
  import struct
5
5
  import sys
6
6
 
7
- from .icon import png_to_rgb565, png_to_rgb565a8
7
+ from .icon import png_to_indexed_4bpp, png_to_rgb565, png_to_rgb565a8
8
8
  from .utils import checksum_bytes, checksum_file, deflate_raw
9
9
 
10
10
 
@@ -13,15 +13,23 @@ IMAGE_VERSION = 1
13
13
 
14
14
  FORMAT_RGB565 = 1
15
15
  FORMAT_RGB565A8 = 2
16
+ FORMAT_INDEXED_4BPP = 3
16
17
  FORMAT_BY_NAME = {
17
18
  "rgb565": FORMAT_RGB565,
18
19
  "rgb565a8": FORMAT_RGB565A8,
20
+ "indexed_4bpp": FORMAT_INDEXED_4BPP,
19
21
  }
20
22
 
21
23
  COMPRESSION_NONE = 0
22
24
  COMPRESSION_DEFLATE_RAW = 1
23
25
 
24
26
  DEFAULT_ICON_VARIANTS = (32, 64)
27
+ DEFAULT_BACKGROUND_VARIANTS = {
28
+ "full": {"width": 240, "height": 320, "format": "rgb565", "output": "bg/bg_full.gimg"},
29
+ "half": {"width": 120, "height": 160, "format": "indexed_4bpp", "output": "bg/bg_half.gimg"},
30
+ "tiny": {"width": 80, "height": 107, "format": "indexed_4bpp", "output": "bg/bg_tiny.gimg"},
31
+ "tile": {"width": 32, "height": 32, "format": "indexed_4bpp", "output": "bg/bg_tile.gimg"},
32
+ }
25
33
  VARIANT_LABELS = {
26
34
  32: "s",
27
35
  64: "l",
@@ -50,6 +58,8 @@ def _image_payload(src: pathlib.Path, width: int, height: int, fmt: str) -> byte
50
58
  return png_to_rgb565a8(src, width, height)
51
59
  if fmt == "rgb565":
52
60
  return png_to_rgb565(src, width, height)
61
+ if fmt == "indexed_4bpp":
62
+ return png_to_indexed_4bpp(src, width, height)
53
63
  raise ValueError(f"unsupported image format: {fmt}")
54
64
 
55
65
 
@@ -69,7 +79,9 @@ def write_asset_image(
69
79
  raw = _image_payload(src, width, height, fmt)
70
80
  payload = raw
71
81
  compression = COMPRESSION_NONE
72
- if compress:
82
+ if compress and fmt != "indexed_4bpp":
83
+ # Indexed 4bpp is always stored uncompressed — the firmware rejects
84
+ # deflated indexed payloads, and 4-bit data doesn't deflate well.
73
85
  compressed = deflate_raw(raw)
74
86
  if len(compressed) < len(raw):
75
87
  payload = compressed
@@ -112,6 +124,28 @@ def _parse_variant_sizes(values) -> tuple[int, ...]:
112
124
  return sizes
113
125
 
114
126
 
127
+ def _background_variant_specs(spec: dict) -> dict:
128
+ variants = spec.get("variants", DEFAULT_BACKGROUND_VARIANTS)
129
+ if variants is True:
130
+ variants = DEFAULT_BACKGROUND_VARIANTS
131
+ if not isinstance(variants, dict):
132
+ raise ValueError("background variants must be an object or true")
133
+
134
+ out = {}
135
+ for name, defaults in DEFAULT_BACKGROUND_VARIANTS.items():
136
+ value = variants.get(name)
137
+ if value is False or value is None:
138
+ continue
139
+ if value is True:
140
+ value = {}
141
+ if not isinstance(value, dict):
142
+ raise ValueError(f"background variant {name!r} must be an object, true, or false")
143
+ merged = dict(defaults)
144
+ merged.update(value)
145
+ out[name] = merged
146
+ return out
147
+
148
+
115
149
  def _copy_runtime_manifest(manifest: dict) -> dict:
116
150
  runtime = dict(manifest)
117
151
  for key in (
@@ -207,6 +241,24 @@ def make_asset_pack(
207
241
  if not src.is_file():
208
242
  print(f"warning: background source not found: {src}", file=sys.stderr)
209
243
  continue
244
+
245
+ if spec.get("variants") is not None or key == "background":
246
+ backgrounds = dict(runtime_manifest.get("backgrounds", {}))
247
+ for variant_name, variant in _background_variant_specs(spec).items():
248
+ rel_out = variant.get("output", f"bg/bg_{variant_name}.gimg")
249
+ write_asset_image(
250
+ src,
251
+ package_dir / rel_out,
252
+ int(variant["width"]),
253
+ int(variant["height"]),
254
+ variant.get("format", "indexed_4bpp"),
255
+ compress=compress,
256
+ )
257
+ backgrounds[variant_name] = rel_out
258
+ if backgrounds:
259
+ runtime_manifest["backgrounds"] = backgrounds
260
+ continue
261
+
210
262
  width = int(spec.get("width", 64 if key == "bg_tile" else 240))
211
263
  height = int(spec.get("height", 64 if key == "bg_tile" else 320))
212
264
  fmt = spec.get("format", "rgb565")
@@ -74,7 +74,7 @@ def main(argv=None) -> int:
74
74
  p_asset_image.add_argument("--out", required=True, help="Output .gimg path")
75
75
  p_asset_image.add_argument("--width", type=int, required=True, help="Output width in pixels")
76
76
  p_asset_image.add_argument("--height", type=int, required=True, help="Output height in pixels")
77
- p_asset_image.add_argument("--format", choices=["rgb565", "rgb565a8"], default="rgb565a8",
77
+ p_asset_image.add_argument("--format", choices=["rgb565", "rgb565a8", "indexed_4bpp"], default="rgb565a8",
78
78
  help="Pixel format (default: rgb565a8)")
79
79
  p_asset_image.add_argument("--no-compress", action="store_true", help="Store raw payload without deflate compression")
80
80
 
@@ -163,3 +163,71 @@ def png_to_rgb565(src: pathlib.Path, width: int, height: int) -> bytes:
163
163
  out[i * 2] = rgb565 & 0xFF
164
164
  out[i * 2 + 1] = (rgb565 >> 8) & 0xFF
165
165
  return bytes(out)
166
+
167
+
168
+ def png_to_indexed_4bpp(src: pathlib.Path, width: int, height: int) -> bytes:
169
+ """Quantize an RGBA PNG to a 16-color palette and emit [palette][pixels].
170
+
171
+ Output layout (matches the firmware's GIMG_FORMAT_INDEXED_4BPP):
172
+ - 64 bytes: 16 x lv_color32_t palette in (B, G, R, A) memory order
173
+ - ceil(W*H/2) bytes: packed 4-bit indices, low nibble = even pixel
174
+ """
175
+ in_w, in_h, rgba = _read_png_rgba(src)
176
+ rgba = _resize_nearest(in_w, in_h, rgba, width, height)
177
+ pixel_count = width * height
178
+
179
+ # Coarse-quantize each pixel to a 4-4-4-1 key and tally occurrences.
180
+ # 4-4-4 RGB gives 4096 buckets; we keep the 16 most populated.
181
+ coarse_counts: dict[tuple[int, int, int, int], int] = {}
182
+ pixel_keys: list[tuple[int, int, int, int]] = [None] * pixel_count
183
+ for i in range(pixel_count):
184
+ r, g, b, a = rgba[i * 4:i * 4 + 4]
185
+ key = (r >> 4, g >> 4, b >> 4, 1 if a >= 128 else 0)
186
+ pixel_keys[i] = key
187
+ coarse_counts[key] = coarse_counts.get(key, 0) + 1
188
+
189
+ sorted_keys = sorted(coarse_counts.items(), key=lambda kv: -kv[1])
190
+ palette_keys = [k for k, _ in sorted_keys[:16]]
191
+ while len(palette_keys) < 16:
192
+ palette_keys.append((0, 0, 0, 1))
193
+
194
+ # Map each pixel to a palette slot. In-gamut pixels hit the dict; the
195
+ # remainder fall back to nearest-neighbour in 4-4-4-1 space.
196
+ key_to_pal = {k: i for i, k in enumerate(palette_keys)}
197
+ pixel_indices = bytearray(pixel_count)
198
+ for i, key in enumerate(pixel_keys):
199
+ idx = key_to_pal.get(key)
200
+ if idx is not None:
201
+ pixel_indices[i] = idx
202
+ continue
203
+ best = 0
204
+ best_d = 0x7FFFFFFF
205
+ for j, pk in enumerate(palette_keys):
206
+ d = (
207
+ (key[0] - pk[0]) ** 2
208
+ + (key[1] - pk[1]) ** 2
209
+ + (key[2] - pk[2]) ** 2
210
+ + (key[3] - pk[3]) ** 2
211
+ )
212
+ if d < best_d:
213
+ best_d = d
214
+ best = j
215
+ pixel_indices[i] = best
216
+
217
+ palette_bytes = bytearray()
218
+ for qr, qg, qb, qa in palette_keys:
219
+ r = (qr << 4) | 0x0F
220
+ g = (qg << 4) | 0x0F
221
+ b = (qb << 4) | 0x0F
222
+ a = 255 if qa else 0
223
+ palette_bytes.extend(struct.pack("<BBBB", b, g, r, a))
224
+
225
+ packed_count = (pixel_count + 1) // 2
226
+ pixel_bytes = bytearray(packed_count)
227
+ for i in range(pixel_count):
228
+ if i & 1:
229
+ pixel_bytes[i >> 1] |= pixel_indices[i] << 4
230
+ else:
231
+ pixel_bytes[i >> 1] |= pixel_indices[i]
232
+
233
+ return bytes(palette_bytes) + bytes(pixel_bytes)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ghostbt
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Ghost Build Tool - build and package GhostESP native SD apps
5
5
  License-Expression: GPL-3.0
6
6
  Project-URL: Homepage, https://github.com/GhostESP-Revival/GhostESP
@@ -131,17 +131,31 @@ When loading a `.gtheme`, firmware streams entries into `/mnt/ghostesp/themes/ac
131
131
 
132
132
  ### PSRAM vs internal RAM
133
133
 
134
- **PSRAM devices:** Up to 32 icons cached in PSRAM, tiled or scaled backgrounds supported. Background images smaller than the screen tile automatically; larger images scale to fill using LVGL zoom. A 128x128 background will scale up to fill any screen. Background images are always loaded into PSRAM.
134
+ **PSRAM devices:** Up to 32 icons cached in PSRAM, tiled or scaled backgrounds supported. Background images smaller than the screen tile automatically; larger images scale to fill using LVGL zoom. A 128x128 background will scale up to fill any screen. Background images are always loaded into PSRAM. Any image format works; deflate-compressed payloads decode into PSRAM.
135
135
 
136
- **Internal-only devices:** 2 icons cached in internal RAM, no background images. Icons fall back to compiled-in artwork when cache is full. The pack still loads colors and icon mappings; only the image cache is limited.
136
+ **Internal-only devices:** 2 icons cached in internal RAM. Background tile (≤32x32) supported — LVGL tiles the small image across the screen. Icon size capped at 32x32 RGB565A8; deflate-compressed payloads rejected. Use the `indexed_4bpp` format for icons and the background tile to fit in internal RAM: a 32x32 indexed icon is 576 bytes vs 3,072 bytes for RGB565A8 (~5x smaller). Icons fall back to compiled-in artwork when the cache is full. The pack still loads colors and icon mappings; only the image cache is limited.
137
+
138
+ ### Image formats
139
+
140
+ | Format | Code | Size at 32x32 | Notes |
141
+ |--------|------|---------------|-------|
142
+ | `rgb565` | 1 | 2,048 B | No alpha. Best for opaque backgrounds. |
143
+ | `rgb565a8` | 2 | 3,072 B | RGB565 + separate alpha plane. Default for icons. |
144
+ | `indexed_4bpp` | 3 | 576 B | 16-color palette, packed 4-bit pixels. Ideal for internal-RAM devices; the `gbt` tool quantizes the source PNG at build time. |
145
+
146
+ Set the pack-wide icon format with `icon_format` in the source manifest, or override per-background via the `format` field on each `background_sources` entry. Indexed payloads are always stored uncompressed.
137
147
 
138
148
  Convert one image directly:
139
149
 
140
150
  ```bash
151
+ # Default — RGB565A8, 3 KB for 32x32
141
152
  gbt asset image ./wifi.png --out ./wifi_l.gimg --width 64 --height 64 --format rgb565a8
153
+
154
+ # Internal-RAM friendly — indexed 4bpp, 576 B for 32x32
155
+ gbt asset image ./wifi.png --out ./wifi_l.gimg --width 64 --height 64 --format indexed_4bpp
142
156
  ```
143
157
 
144
- The generated pack folder contains `manifest.json`, `checksums.json`, and generated `.gimg` files. Asset packs store raw `.gimg` payloads by default so firmware does not run deflate decompression from small UI task stacks. Missing icons are fine; firmware falls back to built-in artwork for any icon not present in the selected pack. Background tiles are loaded from PSRAM only.
158
+ The generated pack folder contains `manifest.json`, `checksums.json`, and generated `.gimg` files. Asset packs store raw `.gimg` payloads by default so firmware does not run deflate decompression from small UI task stacks. Missing icons are fine; firmware falls back to built-in artwork for any icon not present in the selected pack. Full-screen background images are loaded into PSRAM only; the smaller tiled background path works on internal-RAM devices at ≤32x32.
145
159
 
146
160
  ## Requirements
147
161
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ghostbt"
7
- version = "0.2.0"
7
+ version = "0.2.2"
8
8
  description = "Ghost Build Tool - build and package GhostESP native SD apps"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -1 +0,0 @@
1
- __version__ = "0.2.0"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes