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.
- {ghostbt-0.2.0 → ghostbt-0.2.2}/PKG-INFO +18 -4
- {ghostbt-0.2.0 → ghostbt-0.2.2}/README.md +17 -3
- ghostbt-0.2.2/ghostbt/__init__.py +1 -0
- {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/asset.py +54 -2
- {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/cli.py +1 -1
- {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/icon.py +68 -0
- {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt.egg-info/PKG-INFO +18 -4
- {ghostbt-0.2.0 → ghostbt-0.2.2}/pyproject.toml +1 -1
- ghostbt-0.2.0/ghostbt/__init__.py +0 -1
- {ghostbt-0.2.0 → ghostbt-0.2.2}/LICENSE +0 -0
- {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/__main__.py +0 -0
- {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/build.py +0 -0
- {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/config.py +0 -0
- {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/create.py +0 -0
- {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/data/sdk/ghostesp_plugin_api.h +0 -0
- {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/data/templates/basic_app/CMakeLists.txt +0 -0
- {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/data/templates/basic_app/main/CMakeLists.txt +0 -0
- {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/data/templates/basic_app/main/idf_component.yml +0 -0
- {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/data/templates/basic_app/main/{{APP_SYMBOL}}.c +0 -0
- {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/data/templates/basic_app/manifest.json +0 -0
- {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/data/templates/basic_app/sdkconfig.defaults +0 -0
- {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/dist.py +0 -0
- {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/esp_idf.py +0 -0
- {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/firmware.py +0 -0
- {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/flash.py +0 -0
- {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/package.py +0 -0
- {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt/utils.py +0 -0
- {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt.egg-info/SOURCES.txt +0 -0
- {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt.egg-info/dependency_links.txt +0 -0
- {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt.egg-info/entry_points.txt +0 -0
- {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt.egg-info/requires.txt +0 -0
- {ghostbt-0.2.0 → ghostbt-0.2.2}/ghostbt.egg-info/top_level.txt +0 -0
- {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.
|
|
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
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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
|
|
|
@@ -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
|
|
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
|