feat(scripts): add display & drawbuf dump (#8645)
Arduino Lint / lint (push) Has been cancelled
Build Examples with C++ Compiler / build-examples (push) Has been cancelled
MicroPython CI / Build esp32 port (push) Has been cancelled
MicroPython CI / Build rp2 port (push) Has been cancelled
MicroPython CI / Build stm32 port (push) Has been cancelled
MicroPython CI / Build unix port (push) Has been cancelled
C/C++ CI / Build OPTIONS_16BIT - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_24BIT - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_FULL_32BIT - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_NORMAL_8BIT - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_SDL - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_VG_LITE - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_16BIT - cl - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_16BIT - gcc - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_24BIT - cl - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_24BIT - gcc - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_FULL_32BIT - cl - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_FULL_32BIT - gcc - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_VG_LITE - cl - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_VG_LITE - gcc - Windows (push) Has been cancelled
C/C++ CI / Build ESP IDF ESP32S3 (push) Has been cancelled
C/C++ CI / Run tests with 32bit build (push) Has been cancelled
C/C++ CI / Run tests with 64bit build (push) Has been cancelled
BOM Check / bom-check (push) Has been cancelled
Verify that lv_conf_internal.h matches repository state / verify-conf-internal (push) Has been cancelled
Verify the widget property name / verify-property-name (push) Has been cancelled
Verify code formatting / verify-formatting (push) Has been cancelled
Compare file templates with file names / template-check (push) Has been cancelled
Build docs / build-and-deploy (push) Has been cancelled
Test API JSON generator / Test API JSON (push) Has been cancelled
Check Makefile / Build using Makefile (push) Has been cancelled
Check Makefile for UEFI / Build using Makefile for UEFI (push) Has been cancelled
Emulated Performance Test / ARM Emulated Benchmark - Script Check (scripts/perf/tests/benchmark_results_comment/test.sh) (push) Has been cancelled
Emulated Performance Test / ARM Emulated Benchmark - Script Check (scripts/perf/tests/filter_docker_logs/test.sh) (push) Has been cancelled
Emulated Performance Test / ARM Emulated Benchmark - Script Check (scripts/perf/tests/serialize_results/test.sh) (push) Has been cancelled
Hardware Performance Test / Hardware Performance Benchmark (push) Has been cancelled
Hardware Performance Test / HW Benchmark - Save PR Number (push) Has been cancelled
Performance Tests CI / Perf Tests OPTIONS_TEST_PERF_32B - Ubuntu (push) Has been cancelled
Performance Tests CI / Perf Tests OPTIONS_TEST_PERF_64B - Ubuntu (push) Has been cancelled
Port repo release update / run-release-branch-updater (push) Has been cancelled
Verify Font License / verify-font-license (push) Has been cancelled
Verify Kconfig / verify-kconfig (push) Has been cancelled
Emulated Performance Test / ARM Emulated Benchmark 32b - lv_conf_perf32b (push) Has been cancelled
Emulated Performance Test / ARM Emulated Benchmark 64b - lv_conf_perf64b (push) Has been cancelled
Emulated Performance Test / ARM Emulated Benchmark - Save PR Number (push) Has been cancelled
Close stale issues and PRs / stale (push) Has been cancelled

Signed-off-by: rongyichang <rongyichang@xiaomi.com>
This commit is contained in:
terry.rong
2025-08-05 09:38:54 +08:00
committed by GitHub
parent 3ce99c5492
commit 3fae9b9a10
2 changed files with 279 additions and 6 deletions
+1
View File
@@ -7,6 +7,7 @@ Debugger()
# Dumps # Dumps
DumpObj() DumpObj()
DumpDisplayBuf()
# Infos # Infos
InfoStyle() InfoStyle()
+278 -6
View File
@@ -1,5 +1,8 @@
import argparse import argparse
from typing import Iterator, Union, Optional from typing import Iterator, Union, Optional
from PIL import Image
import numpy as np
from pathlib import Path
import gdb import gdb
@@ -114,7 +117,9 @@ class LVObject(Value):
for i in range(count): for i in range(count):
style = styles[i].style style = styles[i].style
prop_cnt = style.prop_cnt prop_cnt = style.prop_cnt
values_and_props = style.values_and_props.cast("lv_style_const_prop_t", ptr=True) values_and_props = style.values_and_props.cast(
"lv_style_const_prop_t", ptr=True
)
for j in range(prop_cnt): for j in range(prop_cnt):
prop = values_and_props[j].prop prop = values_and_props[j].prop
if prop == LV_STYLE_PROP_INV or prop == LV_STYLE_PROP_ANY: if prop == LV_STYLE_PROP_INV or prop == LV_STYLE_PROP_ANY:
@@ -122,9 +127,7 @@ class LVObject(Value):
yield values_and_props[j] yield values_and_props[j]
def get_child(self, index: int): def get_child(self, index: int):
return ( return self.spec_attr.children[index] if self.spec_attr else None
self.spec_attr.children[index] if self.spec_attr else None
)
class LVDisplay(Value): class LVDisplay(Value):
@@ -133,12 +136,222 @@ class LVDisplay(Value):
def __init__(self, disp: Value): def __init__(self, disp: Value):
super().__init__(disp) super().__init__(disp)
@property
def hor_res(self) -> int:
"""Get horizontal resolution in pixels"""
return int(self.super_value("hor_res"))
@property
def ver_res(self) -> int:
"""Get vertical resolution in pixels"""
return int(self.super_value("ver_res"))
@property @property
def screens(self): def screens(self):
screens = self.super_value("screens") screens = self.super_value("screens")
for i in range(self.screen_cnt): for i in range(self.screen_cnt):
yield LVObject(screens[i]) yield LVObject(screens[i])
# Buffer-related properties
@property
def buf_1(self):
"""Get first draw buffer (may be None)"""
buf_ptr = self.super_value("buf_1")
return LVDrawBuf(buf_ptr) if buf_ptr else None
@property
def buf_2(self):
"""Get second draw buffer (may be None)"""
buf_ptr = self.super_value("buf_2")
return LVDrawBuf(buf_ptr) if buf_ptr else None
@property
def buf_act(self):
"""Get currently active draw buffer (may be None)"""
buf_ptr = self.super_value("buf_act")
return LVDrawBuf(buf_ptr) if buf_ptr else None
class LVDrawBuf(Value):
"""LVGL draw buffer"""
_color_formats = {
"RGB565": None,
"RGB888": None,
"ARGB8888": None,
"XRGB8888": None,
}
_bpp = {
"RGB565": 16,
"RGB888": 24,
"ARGB8888": 32,
"XRGB8888": 32,
}
def __init__(self, draw_buf: Value):
super().__init__(draw_buf)
self._init_color_formats()
def _init_color_formats(self):
"""init color formats from enum"""
for fmt in self._color_formats.keys():
if self._color_formats[fmt] is None:
try:
self._color_formats[fmt] = int(
gdb.parse_and_eval(f"LV_COLOR_FORMAT_{fmt}")
)
except:
print(f"Warning: Failed to get LV_COLOR_FORMAT_{fmt}")
self._color_formats[fmt] = -1
def color_format_info(self) -> dict:
"""get color format info"""
header = self.super_value("header")
cf = int(header["cf"])
fmt_name = "UNKNOWN"
for name, val in self._color_formats.items():
if val == cf:
fmt_name = name
break
return {"value": cf, "name": fmt_name, "bpp": self._bpp.get(fmt_name, 0)}
@property
def header(self) -> dict:
"""Get the image header information as a dictionary"""
header = self.super_value("header")
return {
"w": header["w"],
"h": header["h"],
"stride": header["stride"],
"cf": header["cf"], # Color format
}
@property
def data_size(self) -> int:
"""Get the total buffer size in bytes"""
return self.super_value("data_size")
@property
def data(self) -> bytes:
"""Get the buffer data ptr"""
return self.super_value("data")
def data_dump(self, filename: str, format: str = None) -> bool:
"""
Dump the buffer data to an image file.
Args:
filename: Output file path
format: Image format (None for auto-detection from filename)
Returns:
bool: True if successful, False otherwise
"""
try:
# Validate input parameters
if not filename:
raise ValueError("Output filename cannot be empty")
# Get buffer metadata
header = self.super_value("header")
width = int(header["w"])
height = int(header["h"])
cf_info = self.color_format_info()
data_ptr = self.super_value("data")
data_size = int(self.super_value("data_size"))
# Validate buffer data
if not data_ptr:
raise ValueError("Data pointer is NULL")
if width <= 0 or height <= 0:
raise ValueError(f"Invalid dimensions: {width}x{height}")
if data_size <= 0:
raise ValueError(f"Invalid data size: {data_size}")
# Read pixel data
pixel_data = (
gdb.selected_inferior().read_memory(int(data_ptr), data_size).tobytes()
)
if not pixel_data:
raise ValueError("Failed to read pixel data")
# Process based on color format
img = self._convert_to_image(pixel_data, width, height, cf_info["value"])
if img is None:
return False
# Determine output format
output_format = (
format.upper() if format else Path(filename).suffix[1:].upper() or "BMP"
)
# Save image
img.save(filename, format=output_format)
print(
f"Successfully saved {cf_info['name']} buffer as {output_format} to {filename}"
)
return True
except gdb.MemoryError:
print("Error: Failed to access memory")
return False
except ValueError as e:
print(f"Validation error: {str(e)}")
return False
except Exception as e:
print(f"Unexpected error: {str(e)}")
return False
def _convert_to_image(
self, pixel_data: bytes, width: int, height: int, color_format: int
) -> Optional[Image.Image]:
"""
Convert raw pixel data to PIL Image based on color format.
Args:
pixel_data: Raw pixel bytes
width: Image width
height: Image height
color_format: LVGL color format value
Returns:
PIL.Image or None if conversion fails
"""
try:
if color_format == self._color_formats["RGB565"]:
# Convert RGB565 to RGB888
arr = np.frombuffer(pixel_data, dtype=np.uint8)
arr = arr.reshape((height, width, 2))
rgb565 = np.frombuffer(pixel_data, dtype=np.uint16).reshape((height, width))
r = (((rgb565 & 0xF800) >> 11) << 3).astype(np.uint8)
g = ((rgb565 & 0x07E0) >> 3).astype(np.uint8)
b = ((rgb565 & 0x001F) << 3).astype(np.uint8)
return Image.fromarray(np.dstack((r, g, b)), "RGB")
elif color_format == self._color_formats["RGB888"]:
arr = np.frombuffer(pixel_data, dtype=np.uint8).reshape(height, width, 3)
rgb_arr = arr[:, :, [2, 1, 0]] # BGR -> RGB
return Image.fromarray(rgb_arr, "RGB")
elif color_format == self._color_formats["ARGB8888"]:
arr = np.frombuffer(pixel_data, dtype=np.uint8).reshape(-1, 4)
rgba = np.column_stack((arr[:, 1:4], arr[:, 0])) # ARGB -> RGBA
return Image.frombytes("RGBA", (width, height), rgba.tobytes())
elif color_format == self._color_formats["XRGB8888"]:
arr = np.frombuffer(pixel_data, dtype=np.uint8).reshape(-1, 4)
rgb = arr[:, [2, 1, 0]] # BGR -> RGB
return Image.fromarray(rgb.reshape(height, width, 3), "RGB")
raise ValueError(f"Unsupported color format: 0x{color_format:x}")
except Exception as e:
print(f"Conversion error: {str(e)}")
return None
class LVGL: class LVGL:
"""LVGL instance""" """LVGL instance"""
@@ -154,6 +367,10 @@ class LVGL:
for disp in LVList(ll, "lv_display_t"): for disp in LVList(ll, "lv_display_t"):
yield LVDisplay(disp) yield LVDisplay(disp)
def disp_default(self):
disp_default = self.lv_global.disp_default
return LVDisplay(disp_default) if disp_default else None
def screen_active(self): def screen_active(self):
disp = self.lv_global.disp_default disp = self.lv_global.disp_default
return disp.act_scr if disp else None return disp.act_scr if disp else None
@@ -182,7 +399,9 @@ def set_lvgl_instance(lv_global: Union[gdb.Value, Value, None]):
inited = lv_global.inited inited = lv_global.inited
if not inited: if not inited:
print("\x1b[31mlvgl is not initialized yet. Please call `set_lvgl_instance(None)` later.\x1b[0m") print(
"\x1b[31mlvgl is not initialized yet. Please call `set_lvgl_instance(None)` later.\x1b[0m"
)
return return
g_lvgl_instance = LVGL(lv_global) g_lvgl_instance = LVGL(lv_global)
@@ -259,6 +478,55 @@ class DumpObj(gdb.Command):
self.dump_obj(screen, depth=depth + 1, limit=args.level) self.dump_obj(screen, depth=depth + 1, limit=args.level)
class DumpDisplayBuf(gdb.Command):
"""dump display buf to image"""
def __init__(self):
super(DumpDisplayBuf, self).__init__(
"dump display", gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION
)
def invoke(self, args, from_tty):
parser = argparse.ArgumentParser(description="Dump display draw buffer.")
parser.add_argument(
"-p",
"--prefix",
type=str,
default="",
help="prefix of dump file path",
)
parser.add_argument(
"-f",
"--format",
type=str,
choices=["bmp", "png"],
default=None,
help="dump file format (bmp or png)",
)
try:
args = parser.parse_args(gdb.string_to_argv(args))
except SystemExit:
return
display = g_lvgl_instance.disp_default()
if not display:
print("Error: Invalid display pointer")
return
buffers = {
"buf_1": display.buf_1,
"buf_2": display.buf_2,
}
for buf_name, buf_ptr in buffers.items():
if buf_ptr is not None:
draw_buf = LVDrawBuf(buf_ptr)
filename = f"{args.prefix}{buf_name}.{args.format.lower() if args.format else 'bmp'}"
draw_buf.data_dump(filename, args.format)
else:
print(f"Warning: {buf_name} buffer is None, skipping.")
class InfoStyle(gdb.Command): class InfoStyle(gdb.Command):
"""dump obj style value for specified obj""" """dump obj style value for specified obj"""
@@ -328,7 +596,11 @@ class InfoDrawUnit(gdb.Command):
} }
type = types.get(name, lookup_type("lv_draw_unit_t")) type = types.get(name, lookup_type("lv_draw_unit_t"))
print(draw_unit.cast(type, ptr=True).dereference().format_string(pretty_structs=True, symbols=True)) print(
draw_unit.cast(type, ptr=True)
.dereference()
.format_string(pretty_structs=True, symbols=True)
)
def invoke(self, args, from_tty): def invoke(self, args, from_tty):
for unit in g_lvgl_instance.draw_units(): for unit in g_lvgl_instance.draw_units():