chore(deps): 添加 esp_emote_gfx + esp_mmap_assets 组件(Phase 10 依赖)

Phase 10 数字人模式 LVGL → EAF 切换所需的两个新组件:
- espressif2022/esp_emote_gfx v3.0.5
  轻量软件渲染 UI 框架(gfx_emote_init/gfx_disp_add/gfx_anim/gfx_label)
- espressif/esp_mmap_assets v2.0.0
  资源打包加载(虽然 use_fs 模式 buggy,我们绕过它直接 fopen,
  但保留组件以便后续 mmap partition 模式启用)

gfx_touch.c 含我们的 local shim 兼容 esp_lcd_touch v1.1.2 旧 API
(已在前一个 commit 31982ba 中说明)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rdzleo 2026-05-15 15:56:32 +08:00
parent 31982ba7b9
commit 86200f5e3a
195 changed files with 183679 additions and 0 deletions

View File

@ -0,0 +1 @@
a06a58c74f7deb4186460f27f5e6db52fda4c254d7e03c3e05e7987aaf73de1a

View File

@ -0,0 +1,116 @@
# Object files
*.o
*.ko
*.obj
*.elf
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.so
*.so.*
*.dylib
# Executables
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
# =========================
# Operating System Files
# =========================
# Linux
# =========================
# Vim temporary files
*~
*.swp
*.swo
# OSX
# =========================
.DS_Store
.AppleDouble
.LSOverride
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# Windows
# =========================
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
# ESP32
build/
sdkconfig
managed_components/
*.lock
dist/
#Vscoe
.vscode/
.sdkconfig
.sbmp
.bmp
.gif
sdkconfig.old
# Generated documentation
docs/_build/
# Babel/gettext catalogs built by docs/scripts/sync_locale_zh.py
docs/locale/zh_CN/LC_MESSAGES/
docs/doxygen_output/
Doxyfile
API_REFERENCE.md
# Python cache (docs scripts / locale imports)
**/__pycache__/
*.py[cod]

View File

@ -0,0 +1,98 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
default_install_hook_types:
- pre-commit
- commit-msg
exclude: |
(?x)(
.*/assets/.*
| mmap_generate_*.h
| font_puhui_16_4.c
| docs/.*
| png_to_rgb565a8.py
| qrcodegen.*
)
repos:
- repo: https://github.com/igrr/astyle_py.git
rev: v1.0.5
hooks:
- id: astyle_py
args: ['--style=otbs', '--attach-namespaces', '--attach-classes', '--indent=spaces=4', '--convert-tabs', '--align-pointer=name', '--align-reference=name', '--keep-one-line-statements', '--pad-header', '--pad-oper']
- repo: https://github.com/espressif/check-copyright/
rev: v1.0.3
hooks:
- id: check-copyright
args: ['--config', 'check_copyright_config.yaml']
- repo: https://github.com/PyCQA/flake8
rev: 5.0.4
hooks:
- id: flake8
types: [python]
args: ['--config=.flake8', '--tee', '--benchmark']
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
# note: whitespace exclusions use multiline regex, see https://pre-commit.com/#regular-expressions
# items are:
# 1 - some file extensions
# 2 - any file matching *test*/*expected* (for host tests, if possible use this naming pattern always)
# 3 - any file with known-warnings in the name
# 4 - any directory named 'testdata'
# 5 - protobuf auto-generated files
exclude: &whitespace_excludes |
(?x)^(
.+\.(md|rst|map|bin)|
.+test.*\/.*expected.*|
.+known-warnings.*|
.+\/testdata\/.+|
.*_pb2.py|
.*.pb-c.h|
.*.pb-c.c|
.*.yuv
)$
- id: end-of-file-fixer
exclude: *whitespace_excludes
- id: check-executables-have-shebangs
- id: check-shebang-scripts-are-executable
- id: mixed-line-ending
args: ['-f=lf']
- id: double-quote-string-fixer
- id: no-commit-to-branch
name: Do not use more than one slash in the branch name
args: ['--pattern', '^[^/]*/[^/]*/']
- id: no-commit-to-branch
name: Do not use uppercase letters in the branch name
args: ['--pattern', '^[^A-Z]*[A-Z]']
# - repo: local
# hooks:
# - id: check-executables
# name: Check File Permissions
# entry: .gitlab/tools/check_executables.py --action executables
# language: python
# types: [executable]
# exclude: '\.pre-commit/.+'
# - id: check-executable-list
# name: Validate executable-list.txt
# entry: .gitlab/tools/check_executables.py --action list
# language: python
# pass_filenames: false
# always_run: true
- repo: https://github.com/espressif/conventional-precommit-linter
rev: v1.8.0
hooks:
- id: conventional-precommit-linter
stages: [commit-msg]
args:
- --subject-min-length=15
- --body-max-line-length=200
- repo: https://github.com/codespell-project/codespell
rev: v2.3.0
hooks:
- id: codespell
args: [-w]

View File

@ -0,0 +1,88 @@
# Changelog
All notable changes to the ESP Emote GFX component will be documented in this file.
## [3.0.5] - 2026-04-30
- Add motion scene widget documentation covering `gfx_motion`, `gfx_motion_scene`, asset layout, and runtime usage
- Add motion widget example references to README and Sphinx docs
- Simplify the motion rendering path by removing NanoVG and libtess2 dependencies
- Keep polygon fill on the internal scanline fallback path for a leaner release footprint
## [3.0.4] - 2026-04-21
- restore gfx_disp_event_t
- Render loop: sleep `GFX_RENDER_TASK_IDLE_SLEEP_MS` once before the main loop so the first frame is not driven until the caller can finish setup after `add_disp()` (avoids a startup deadlock)
## [3.0.3] - 2026-04-20
- Add `gfx_button` widget (text, font, normal/pressed colors, border)
- Add `gfx_log` API for log level configuration
- Documentation: separate English and Simplified Chinese HTML builds (gettext), language switcher, unified `postprocess_docs.sh` pipeline (API RST, Sphinx, Doxygen)
- Simplify GitHub Actions documentation job to a single build step
## [3.0.2] - 2026-04-17
- Update version of esp_new_jpeg
## [3.0.1] - 2026-02-13
- Add CI build action for P4
- Optimize multi-buffer switching logic
- Fix crash when text is NULL
- Fix missing API documentation (e.g. gfx_touch_add)
## [3.0.0] - 2026-01-22
- Add documentation build action
- Optimize EAF 8-bit render
- Fix FreeType parsing performance
- Remove duplicated label-related APIs
## [2.1.0] - 2026-01-28
- Support for decoding Heatshrink-compressed image slices
## [2.0.4] - 2026-01-22
- Fix Huffman+RLE decoding buffer sizing to prevent oversized output errors (Issue [#18](https://github.com/espressif2022/esp_emote_gfx/issues/18))
## [2.0.3] - 2026-01-08
- Delete local assets
- Build acion for ['release-v5.2', 'release-v5.3', 'release-v5.4', 'release-v5.5']
- Fix ESP-IDF version compatibility issues
- Change flush_callback timeout from 20 ms to wait forever
## [2.0.2] - 2025-12-26
- Add optional JPEG decoding support for EAF animations
- Center QR code rendering in UI layout
- Add alpha channel support for animations
## [2.0.1] - 2025-12-05
- Add Touch event
## [2.0.0] - 2025-12-01
- Added partial refresh mode support
- Added QR code widget (gfx_qrcode)
## [1.2.0] - 2025-09-0
- use eaf as a lib
## [1.1.2] - 2025-09-29
### Upgrade dependencies
- Update `espressif/esp_new_jpeg` to 0.6.x by @Kevincoooool. [#8](https://github.com/espressif2022/esp_emote_gfx/pull/8)
## [1.1.1] - 2025-09-23
### Fixed
- Resolve image block decoding failure in specific cases. [#6](https://github.com/espressif2022/esp_emote_gfx/issues/6)
## [1.0.0] - 2025-08-01
### Added
- Initial release of ESP Emote GFX framework
- Core graphics rendering engine
- Object system for images and labels
- Basic drawing functions and color utilities
- Software blending capabilities
- Timer system for animations
- Support for ESP-IDF 5.0+
- FreeType font rendering integration
- JPEG image decoding support
### Features
- Lightweight graphics framework optimized for embedded systems
- Memory-efficient design for resource-constrained environments

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,23 @@
# Keep source discovery automatic while ensuring newly added files trigger
# CMake regeneration in normal configure mode. ESP-IDF also evaluates component
# CMakeLists in script mode while collecting requirements, where
# CONFIGURE_DEPENDS is not accepted.
if(CMAKE_SCRIPT_MODE_FILE)
file(GLOB_RECURSE SRC_FILES "src/*.c")
else()
file(GLOB_RECURSE SRC_FILES CONFIGURE_DEPENDS "src/*.c")
endif()
set(PRIV_INCLUDE_DIRS
"src"
)
idf_component_register(
SRCS
${SRC_FILES}
INCLUDE_DIRS
"include"
PRIV_INCLUDE_DIRS
${PRIV_INCLUDE_DIRS}
REQUIRES esp_timer
)

View File

@ -0,0 +1,152 @@
menu "ESP Emote Graphics Framework"
config GFX_FONT_FREETYPE_SUPPORT
bool "Enable FreeType font support"
default n
help
Enable support for FreeType fonts (TTF/OTF).
This requires the FreeType library to be enabled.
When enabled, you can use TrueType and OpenType fonts.
When disabled, only LVGL C format fonts are supported.
config GFX_EAF_JPEG_DECODE_SUPPORT
bool "Enable JPEG decoding support in EAF"
default y
help
Enable support for JPEG decoding in EAF (Emote Animation Format).
This requires the ESP JPEG decoder component to be enabled.
When enabled, EAF files can use JPEG encoding for image blocks.
When disabled, JPEG encoding will not be available, reducing code size.
config GFX_EAF_HEATSHRINK_SUPPORT
bool "Enable Heatshrink support in EAF"
default y
depends on HEATSHRINK_DYNAMIC_ALLOC || (HEATSHRINK_STATIC_WINDOW_BITS = 8 && HEATSHRINK_STATIC_LOOKAHEAD_BITS = 4)
help
Enable support for Heatshrink decoding in EAF (Emote Animation Format).
Note: Only supports 8-bit window and 4-bit lookahead if dynamic allocation is disabled.
comment "Heatshrink support is unavailable due to static bit mismatch"
depends on !HEATSHRINK_DYNAMIC_ALLOC && (HEATSHRINK_STATIC_WINDOW_BITS != 8 || HEATSHRINK_STATIC_LOOKAHEAD_BITS != 4)
menu "Software Blend"
config GFX_BLEND_TRI_EDGE_AA_RANGE
int "Triangle edge AA range"
range 0 4096
default 0
help
Edge anti-aliasing distance threshold for the software
triangle rasterizer, in mesh sub-pixel units. 0 means one
logical pixel in the mesh fixed-point format (256 for Q8).
config GFX_MESH_IMG_SCANLINE_MAX_VERTS
int "Mesh image scanline fill max vertices"
range 16 2048
default 512
help
Maximum polygon vertices accepted by mesh_img scanline fill.
Higher values support more complex filled motion paths, but
increase per-object scratch memory when scanline fill is used.
config GFX_BLEND_POLYGON_MAX_INTERSECTIONS
int "Polygon fill max scanline intersections"
range 16 256
default 64
help
Maximum number of edge intersections stored per polygon scanline
sample. 32 is faster and enough for simple convex-ish shapes.
Increase to 64/128 for complex filled Bezier loops to avoid
clipped or broken scanline spans.
config GFX_BLEND_POLYGON_SUB_SAMPLES
int "Polygon fill vertical AA sub-samples"
range 1 16
default 8
help
Number of vertical sub-samples used by software polygon fill.
Higher values produce smoother coverage but cost more CPU.
config GFX_BLEND_POLYGON_COVERAGE_MAX_WIDTH
int "Polygon fill coverage buffer max width"
range 64 2048
default 512
help
Maximum pixel width of a polygon fill coverage row. Larger
values support wider dirty chunks at the cost of stack memory.
config GFX_BLEND_POLYGON_INWARD_AA
bool "Keep polygon AA inside filled shapes"
default y
help
Do not blend partially covered pixels whose centre is outside
the polygon. This avoids bright/dark halos when solid filled
Bezier parts are drawn over changing framebuffer contents.
config GFX_BLEND_POLYGON_SOLID_HARD_EDGE
bool "Use hard edge for fully opaque polygon fills"
default y
depends on GFX_BLEND_POLYGON_INWARD_AA
help
For opacity=255 polygon fills, write the fill colour directly
for inward edge pixels instead of blending coverage with the
destination framebuffer. This is faster and prevents colour
contamination, but the edge is less smooth.
endmenu
menu "Motion Widget"
config GFX_MOTION_BEZIER_STROKE_SEGS_PER_SEG
int "Bezier stroke samples per cubic segment"
range 2 24
default 6
help
Tessellation samples per cubic Bezier segment for motion
BEZIER_LOOP and BEZIER_STRIP strokes.
config GFX_MOTION_BEZIER_FILL_LOOP_SEGS_PER_SEG
int "Generic Bezier fill samples per cubic segment"
range 2 24
default 12
help
Tessellation samples per cubic Bezier segment for generic
filled closed loops. Higher values produce smoother fill
outlines but increase setup and raster cost.
config GFX_MOTION_BEZIER_FILL_SEGS
int "Preset Bezier fill segments"
range 4 64
default 24
help
Segment count for special 7/13-point eye/ellipse fills.
config GFX_MOTION_HUB_FILL_MAX_POINTS
int "Generic Bezier fill max hub mesh points"
range 64 1024
default 512
help
Scratch point budget for generic filled Bezier loops.
choice GFX_MOTION_BEZIER_FILL_RASTERIZER
prompt "Bezier fill rasterizer"
default GFX_MOTION_BEZIER_FILL_RASTERIZER_SCANLINE
help
Select how BEZIER_FILL mesh objects are rasterized.
Scanline is stable for arbitrary filled paths and avoids
triangle fan artifacts. Mesh triangles use the regular mesh
triangle rasterizer and can produce smoother AA on some simple
cartoon shapes, but may show artifacts on concave paths.
config GFX_MOTION_BEZIER_FILL_RASTERIZER_SCANLINE
bool "Scanline polygon fill"
config GFX_MOTION_BEZIER_FILL_RASTERIZER_TRIANGLE
bool "Mesh triangle fill"
endchoice
endmenu
endmenu

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,111 @@
<p align="center">
<br>
</p>
<h1 align="center">ESP Emote GFX</h1>
<p align="center">
<span>面向嵌入式小屏设备的轻量 UI 图形库</span>
<br>
<sub>Widgets · Text · Images · QR Codes · Animation · Motion Scenes</sub>
<br>
<br>
</p>
<p align="center">
<a href="https://components.espressif.com/components/espressif2022/esp_emote_gfx">
<img src="https://components.espressif.com/components/espressif2022/esp_emote_gfx/badge.svg" alt="Component Registry">
</a>
<a href="https://github.com/espressif2022/esp_emote_gfx/blob/main/LICENSE">
<img src="https://img.shields.io/badge/license-Apache--2.0-blue" alt="License">
</a>
<img src="https://img.shields.io/badge/ESP--IDF-5.0%2B-red" alt="ESP-IDF 5.0+">
</p>
<p align="center">
<img src="https://img.shields.io/badge/rendering-software-2f855a" alt="Software Rendering">
<img src="https://img.shields.io/badge/target-small%20displays-0f766e" alt="Small Displays">
<img src="https://img.shields.io/badge/motion-path%20driven-d97706" alt="Path Driven Motion">
</p>
<p align="center">
<a href="https://espressif2022.github.io/esp_emote_gfx/zh_CN/index.html">中文文档</a> |
<a href="https://espressif2022.github.io/esp_emote_gfx/en/index.html">English Docs</a> |
<a href="https://components.espressif.com/components/espressif2022/esp_emote_gfx">Component Registry</a>
</p>
---
<p align="center">
<strong>把嵌入式小屏 UI 里常见的显示对象、图像、文本、动画、二维码和 Motion 场景,收进一套轻量图形库。</strong>
</p>
<p align="center">
适合资源受限但仍需要流畅动效、清晰文字和轻量交互的小屏产品。
</p>
## 功能框架
<p align="center">
<img src="docs/_static/esp_emote_gfx_framework.svg" alt="ESP Emote GFX framework" width="920">
</p>
## 模块说明
<table>
<tr>
<td width="33%">
<strong>基础控件</strong>
<br>
提供图片、文本、按钮、二维码、动画和 Motion 场景等常用 UI 元素。
</td>
<td width="33%">
<strong>渲染与图像</strong>
<br>
覆盖软件绘制、图像资源、RGB565 / RGB565A8 数据,以及基于控制点的 mesh image 形变。
</td>
<td width="33%">
<strong>文本与字体</strong>
<br>
支持 LVGL bitmap font 和 FreeType TTF/OTF 字体渲染。
</td>
</tr>
<tr>
<td width="33%">
<strong>动画播放</strong>
<br>
负责 EAF 播放、分段控制、循环模式,以及 timer-driven 的状态更新。
</td>
<td width="33%">
<strong>Motion 场景</strong>
<br>
面向路径驱动的角色、表情和交互动效,支持生成式 asset、pose/action 切换和颜色/纹理绑定。
</td>
<td width="33%">
<strong>嵌入式集成</strong>
<br>
作为组件接入工程,连接显示刷新、输入、内存 buffer 和线程安全对象访问。
</td>
</tr>
</table>
## 文档
详细安装、API、示例、Motion 架构和测试工程说明都放在在线文档里:
- 中文文档:<https://espressif2022.github.io/esp_emote_gfx/zh_CN/index.html>
- English docs: <https://espressif2022.github.io/esp_emote_gfx/en/index.html>
- Component Registry: <https://components.espressif.com/components/espressif2022/esp_emote_gfx>
## English
ESP Emote GFX is a lightweight software-rendered graphics library for compact embedded displays that need expressive UI elements without pulling in a heavy graphics stack.
For installation, API references, examples, and motion architecture notes, please visit the online documentation:
- Documentation: <https://espressif2022.github.io/esp_emote_gfx/en/index.html>
- Component Registry: <https://components.espressif.com/components/espressif2022/esp_emote_gfx>
## License
ESP Emote GFX is licensed under the Apache License 2.0. See [LICENSE](LICENSE).

View File

@ -0,0 +1,41 @@
DEFAULT:
perform_check: yes # should the check be performed?
# Sections setting this to 'no' don't need to include any other options as they are ignored
# When a file is using a section with the option set to 'no', no checks are performed.
# what licenses (or license expressions) are allowed for files in this section
# when setting this option in a section, you need to list all the allowed licenses
allowed_licenses:
- Apache-2.0
license_for_new_files: Apache-2.0 # license to be used when inserting a new copyright notice
new_notice_c: | # notice for new C, CPP, H, HPP and LD files
/*
* SPDX-FileCopyrightText: {years} Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: {license}
*/
new_notice_python: | # notice for new python files
# SPDX-FileCopyrightText: {years} Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: {license}
# comment lines matching:
# SPDX-FileCopyrightText: year[-year] Espressif Systems
# or
# SPDX-FileContributor: year[-year] Espressif Systems
# are replaced with this template prefixed with the correct comment notation (# or // or *) and SPDX- notation
espressif_copyright: '{years} Espressif Systems (Shanghai) CO LTD'
# You can create your own rules for files or group of files
examples_and_unit_tests:
include:
- '**/**/test_apps/**'
- 'products/'
allowed_licenses:
- Apache-2.0
- Unlicense
- CC0-1.0
license_for_new_files: CC0-1.0
# ignore: # You can also select ignoring files here
# perform_check: no # Don't check files from that block
# include:

View File

@ -0,0 +1,18 @@
_build/
doxygen_output/
*.pyc
*.pyo
*.pyd
__pycache__/
*.so
*.egg
*.egg-info/
dist/
build/
.tox/
.cache/
.pytest_cache/
htmlcov/
.coverage
.eggs/

View File

@ -0,0 +1,22 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = .
# Default HTML output matches postprocess_docs.sh (English under html/en/)
BUILDDIR = _build/html/en
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

View File

@ -0,0 +1,56 @@
/**
* Minimal overrides: EN/中文 switcher only. Theme colors come from sphinx_idf_theme (light).
*/
.gfx-langbar {
position: fixed;
top: 0;
right: 12px;
z-index: 400;
display: flex;
align-items: center;
gap: 6px;
padding: 6px 10px;
margin: 0;
font-size: 13px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background: rgba(252, 252, 252, 0.95);
color: #404040;
border: 1px solid #d0d0d0;
border-radius: 0 0 6px 6px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
}
.gfx-lang-btn {
color: #2980b9 !important;
text-decoration: none !important;
font-weight: 600;
padding: 2px 6px;
border-radius: 4px;
}
.gfx-lang-btn:hover {
color: #2e8bcc !important;
background: rgba(41, 128, 185, 0.08);
}
.gfx-lang-btn.is-active {
color: #2b2b2b !important;
background: #e8e8e8;
}
.gfx-lang-btn--muted {
opacity: 0.45;
pointer-events: none;
}
.gfx-lang-sep {
opacity: 0.45;
user-select: none;
}
@media (max-width: 768px) {
.wy-nav-content {
padding-top: 2.25rem;
}
}

View File

@ -0,0 +1,88 @@
<svg width="1040" height="520" viewBox="0 0 1040 520" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<style>
.bg { fill: #ffffff; }
.frame { fill: #f8fafc; stroke: #cbd5e1; stroke-width: 1.2; }
.group { fill: #ffffff; stroke: #d8dee8; stroke-width: 1.2; }
.group-soft { fill: #f9fbfd; stroke: #d8dee8; stroke-width: 1.2; }
.accent { fill: #ecfdf5; stroke: #0f766e; stroke-width: 1.4; }
.asset { fill: #fff7ed; stroke: #d97706; stroke-width: 1.4; }
text { font-family: Arial, sans-serif; }
.title { font-size: 28px; font-weight: 700; fill: #111827; }
.caption { font-size: 14px; font-weight: 500; fill: #64748b; }
.section { font-size: 16px; font-weight: 700; fill: #334155; }
.label { font-size: 14px; font-weight: 700; fill: #111827; }
.small { font-size: 12px; font-weight: 500; fill: #64748b; }
.line { stroke: #94a3b8; stroke-width: 1.5; stroke-linecap: round; }
.arrow { fill: #94a3b8; }
</style>
</defs>
<rect class="bg" width="1040" height="520" rx="20"/>
<rect class="frame" x="24" y="24" width="992" height="472" rx="18"/>
<text class="title" x="520" y="62" text-anchor="middle">Functional Framework</text>
<text class="caption" x="520" y="88" text-anchor="middle">Widgets, runtime services, software rendering, and embedded display integration</text>
<rect class="group" x="72" y="120" width="896" height="96" rx="14"/>
<text class="section" x="96" y="150">Widget Layer</text>
<g>
<rect class="accent" x="244" y="138" width="92" height="42" rx="9"/>
<text class="label" x="290" y="164" text-anchor="middle">Label</text>
<rect class="accent" x="352" y="138" width="92" height="42" rx="9"/>
<text class="label" x="398" y="164" text-anchor="middle">Image</text>
<rect class="accent" x="460" y="138" width="92" height="42" rx="9"/>
<text class="label" x="506" y="164" text-anchor="middle">Button</text>
<rect class="accent" x="568" y="138" width="92" height="42" rx="9"/>
<text class="label" x="614" y="164" text-anchor="middle">QR Code</text>
<rect class="accent" x="676" y="138" width="112" height="42" rx="9"/>
<text class="label" x="732" y="164" text-anchor="middle">Animation</text>
<rect class="accent" x="804" y="138" width="126" height="42" rx="9"/>
<text class="label" x="867" y="164" text-anchor="middle">Motion Scene</text>
</g>
<text class="small" x="96" y="184">User-facing UI objects</text>
<text class="small" x="244" y="200">Object properties, layout, invalidation, and drawing callbacks</text>
<line class="line" x1="520" y1="216" x2="520" y2="244"/>
<path class="arrow" d="M520 252L514 242H526L520 252Z"/>
<rect class="group" x="72" y="252" width="896" height="104" rx="14"/>
<text class="section" x="96" y="284">Core Runtime</text>
<g>
<rect class="group-soft" x="244" y="274" width="126" height="48" rx="9"/>
<text class="label" x="307" y="301" text-anchor="middle">Object Tree</text>
<text class="small" x="307" y="316" text-anchor="middle">state + hierarchy</text>
<rect class="group-soft" x="392" y="274" width="136" height="48" rx="9"/>
<text class="label" x="460" y="301" text-anchor="middle">Display Route</text>
<text class="small" x="460" y="316" text-anchor="middle">refresh + flush</text>
<rect class="group-soft" x="550" y="274" width="112" height="48" rx="9"/>
<text class="label" x="606" y="301" text-anchor="middle">Timer</text>
<text class="small" x="606" y="316" text-anchor="middle">animation ticks</text>
<rect class="group-soft" x="684" y="274" width="112" height="48" rx="9"/>
<text class="label" x="740" y="301" text-anchor="middle">Touch</text>
<text class="small" x="740" y="316" text-anchor="middle">input events</text>
</g>
<text class="small" x="96" y="318">Shared services</text>
<text class="small" x="244" y="342">Coordinates widget lifecycle, refresh scheduling, input dispatch, and thread-safe access.</text>
<line class="line" x1="520" y1="356" x2="520" y2="382"/>
<path class="arrow" d="M520 390L514 380H526L520 390Z"/>
<rect class="group" x="72" y="390" width="428" height="72" rx="14"/>
<text class="section" x="96" y="422">Rendering &amp; Assets</text>
<text class="small" x="96" y="446">Software draw · blend · mesh image · fonts · EAF · image converter</text>
<rect class="asset" x="540" y="390" width="428" height="72" rx="14"/>
<text class="section" x="564" y="422">Platform Integration</text>
<text class="small" x="564" y="446">Display buffer · flush callback · input source · component packaging</text>
<line class="line" x1="500" y1="426" x2="540" y2="426"/>
<path class="arrow" d="M548 426L538 420V432L548 426Z"/>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -0,0 +1,52 @@
/**
* ZH / EN switcher: same path under /en/ or /zh_CN/ (GitHub Pages subpath safe).
*/
(function () {
function swapLang(pathname, targetLang) {
var trimmed = pathname.replace(/^\/+|\/+$/g, '');
if (!trimmed) {
return null;
}
var segments = trimmed.split('/');
var i = segments.indexOf('en');
if (i === -1) {
i = segments.indexOf('zh_CN');
}
if (i === -1) {
return null;
}
segments[i] = targetLang;
return '/' + segments.join('/');
}
document.addEventListener('DOMContentLoaded', function () {
var enBtn = document.getElementById('gfx-lang-en');
var zhBtn = document.getElementById('gfx-lang-zh');
if (!enBtn || !zhBtn) {
return;
}
var path = window.location.pathname;
var enHref = swapLang(path, 'en');
var zhHref = swapLang(path, 'zh_CN');
if (!enHref || !zhHref) {
enBtn.classList.add('gfx-lang-btn--muted');
zhBtn.classList.add('gfx-lang-btn--muted');
enBtn.setAttribute('aria-disabled', 'true');
zhBtn.setAttribute('aria-disabled', 'true');
return;
}
enBtn.href = enHref;
zhBtn.href = zhHref;
if (path.indexOf('/zh_CN/') !== -1) {
zhBtn.classList.add('is-active');
zhBtn.setAttribute('aria-current', 'true');
} else {
enBtn.classList.add('is-active');
enBtn.setAttribute('aria-current', 'true');
}
});
})();

View File

@ -0,0 +1,11 @@
{# Extends sphinx_idf_theme: ZH/EN bar + theme CSS (see conf.py html_css_files). #}
{% extends "!layout.html" %}
{% block extrabody %}
{{ super() }}
<div id="gfx-langbar" class="gfx-langbar" role="navigation" aria-label="Language">
<a id="gfx-lang-en" class="gfx-lang-btn" href="#">EN</a>
<span class="gfx-lang-sep" aria-hidden="true">|</span>
<a id="gfx-lang-zh" class="gfx-lang-btn" href="#">中文</a>
</div>
{% endblock %}

View File

@ -0,0 +1,104 @@
Core System (gfx_core)
======================
Types
-----
gfx_core_config_t
~~~~~~~~~~~~~~~~~
.. code-block:: c
typedef struct {
uint32_t fps; /**< Target FPS (frames per second) */
struct {
int task_priority; /**< Render task priority (120) */
int task_stack; /**< Render task stack size (bytes) */
int task_affinity; /**< CPU core (-1: any, 0/1: pinned) */
unsigned task_stack_caps; /**< Stack heap caps (see esp_heap_caps.h) */
} task;
} gfx_core_config_t;
Macros
------
GFX_EMOTE_INIT_CONFIG()
~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: c
#define GFX_EMOTE_INIT_CONFIG() \
Functions
---------
gfx_emote_init()
~~~~~~~~~~~~~~~~
.. code-block:: c
gfx_handle_t gfx_emote_init(const gfx_core_config_t *cfg);
gfx_emote_deinit()
~~~~~~~~~~~~~~~~~~
Deinitialize graphics context
.. code-block:: c
void gfx_emote_deinit(gfx_handle_t handle);
**Parameters:**
* ``handle`` - Graphics handle
gfx_emote_lock()
~~~~~~~~~~~~~~~~
Lock the recursive render mutex to prevent rendering during external operations
.. code-block:: c
esp_err_t gfx_emote_lock(gfx_handle_t handle);
**Parameters:**
* ``handle`` - Graphics handle
**Returns:**
* esp_err_t ESP_OK on success, otherwise an error code
gfx_emote_unlock()
~~~~~~~~~~~~~~~~~~
Unlock the recursive render mutex after external operations
.. code-block:: c
esp_err_t gfx_emote_unlock(gfx_handle_t handle);
**Parameters:**
* ``handle`` - Graphics handle
**Returns:**
* esp_err_t ESP_OK on success, otherwise an error code
gfx_refr_now()
~~~~~~~~~~~~~~
Perform one synchronous refresh (render and flush) immediately. Holds the render mutex for the duration; safe to call from any task.
.. code-block:: c
esp_err_t gfx_refr_now(gfx_handle_t handle);
**Parameters:**
* ``handle`` - Graphics handle
**Returns:**
* esp_err_t ESP_OK on success, otherwise an error code

View File

@ -0,0 +1,271 @@
Display (gfx_disp)
==================
Types
-----
gfx_disp_flush_cb_t
~~~~~~~~~~~~~~~~~~~
.. code-block:: c
typedef void (*gfx_disp_flush_cb_t)(gfx_disp_t *disp, int x1, int y1, int x2, int y2, const void *data);
gfx_disp_update_cb_t
~~~~~~~~~~~~~~~~~~~~
.. code-block:: c
typedef void (*gfx_disp_update_cb_t)(gfx_disp_t *disp, gfx_disp_event_t event, const void *obj);
gfx_disp_event_t
~~~~~~~~~~~~~~~~
.. code-block:: c
typedef enum {
GFX_DISP_EVENT_IDLE = 0,
GFX_DISP_EVENT_ONE_FRAME_DONE,
GFX_DISP_EVENT_PART_FRAME_DONE,
GFX_DISP_EVENT_ALL_FRAME_DONE,
} gfx_disp_event_t;
gfx_perf_counter_t
~~~~~~~~~~~~~~~~~~
.. code-block:: c
typedef struct {
uint64_t calls; /**< Number of API calls */
uint64_t pixels; /**< Processed pixels */
uint64_t time_us; /**< Elapsed time in microseconds */
} gfx_perf_counter_t;
gfx_blend_perf_stats_t
~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: c
typedef struct {
gfx_perf_counter_t fill; /**< gfx_sw_blend_fill_area */
gfx_perf_counter_t color_draw; /**< gfx_sw_blend_draw */
gfx_perf_counter_t image_draw; /**< gfx_sw_blend_img_draw */
gfx_perf_counter_t triangle_draw; /**< gfx_sw_blend_img_triangle_draw */
uint64_t triangle_covered_pixels; /**< Triangle pixels blended (inside + AA) */
uint64_t triangle_aa_pixels; /**< Triangle edge-AA blended pixels */
} gfx_blend_perf_stats_t;
gfx_disp_perf_stats_t
~~~~~~~~~~~~~~~~~~~~~
.. code-block:: c
typedef struct {
uint32_t dirty_pixels; /**< Dirty pixels in the latest rendered frame */
uint64_t frame_time_us; /**< Total frame time */
uint64_t render_time_us; /**< Time spent in render phase */
uint64_t flush_time_us; /**< Time spent in flush callbacks */
uint32_t flush_count; /**< Number of flush calls */
gfx_blend_perf_stats_t blend; /**< Blend-stage details */
} gfx_disp_perf_stats_t;
gfx_disp_config_t
~~~~~~~~~~~~~~~~~
.. code-block:: c
typedef struct {
uint32_t h_res; /**< Screen width in pixels */
uint32_t v_res; /**< Screen height in pixels */
gfx_disp_flush_cb_t flush_cb; /**< Flush callback for this display */
gfx_disp_update_cb_t update_cb; /**< Update callback (frame/playback events) */
void *user_data; /**< User data for this display */
struct {
unsigned char swap : 1; /**< Color swap flag */
unsigned char buff_dma : 1; /**< Alloc buffer with MALLOC_CAP_DMA (internal alloc only) */
unsigned char buff_spiram : 1; /**< Alloc buffer in PSRAM (internal alloc only) */
unsigned char double_buffer : 1; /**< Alloc second buffer for double buffering (internal alloc only) */
unsigned char full_frame : 1; /**< 1 = buf1/buf2 are full-screen framebuffers (e.g. RGB); draw at chunk region. 0 = partition buffer; draw from start. */
} flags;
struct {
void *buf1; /**< Frame buffer 1 (NULL = internal alloc) */
void *buf2; /**< Frame buffer 2 (NULL = internal alloc) */
size_t buf_pixels; /**< Size per buffer in pixels (0 = auto) */
} buffers;
} gfx_disp_config_t;
Functions
---------
gfx_disp_add()
~~~~~~~~~~~~~~
.. code-block:: c
gfx_disp_t * gfx_disp_add(gfx_handle_t handle, const gfx_disp_config_t *cfg);
gfx_disp_del()
~~~~~~~~~~~~~~
Remove a display from the list and release its resources (child list nodes, event group, buffers). Does not free the gfx_disp_t; caller must free(disp) after.
.. code-block:: c
void gfx_disp_del(gfx_disp_t *disp);
**Parameters:**
* ``disp`` - Display from gfx_disp_add; safe to pass NULL
gfx_disp_refresh_all()
~~~~~~~~~~~~~~~~~~~~~~
Invalidate full screen of a display to trigger refresh
.. code-block:: c
void gfx_disp_refresh_all(gfx_disp_t *disp);
**Parameters:**
* ``disp`` - Display from gfx_disp_add
gfx_disp_flush_ready()
~~~~~~~~~~~~~~~~~~~~~~
Notify that flush is done (e.g. from panel IO callback)
.. code-block:: c
bool gfx_disp_flush_ready(gfx_disp_t *disp, bool swap_act_buf);
**Parameters:**
* ``disp`` - Display from gfx_disp_add
* ``swap_act_buf`` - Whether to swap the active buffer
**Returns:**
* bool True on success
gfx_disp_get_user_data()
~~~~~~~~~~~~~~~~~~~~~~~~
Get user data for a display
.. code-block:: c
void * gfx_disp_get_user_data(gfx_disp_t *disp);
**Parameters:**
* ``disp`` - Display from gfx_disp_add
**Returns:**
* void* User data, or NULL
gfx_disp_get_hor_res()
~~~~~~~~~~~~~~~~~~~~~~
Get display horizontal resolution in pixels
.. code-block:: c
uint32_t gfx_disp_get_hor_res(gfx_disp_t *disp);
**Parameters:**
* ``disp`` - Display from gfx_disp_add (NULL allowed; returns default width)
**Returns:**
* uint32_t Width in pixels
gfx_disp_get_ver_res()
~~~~~~~~~~~~~~~~~~~~~~
Get display vertical resolution in pixels
.. code-block:: c
uint32_t gfx_disp_get_ver_res(gfx_disp_t *disp);
**Parameters:**
* ``disp`` - Display from gfx_disp_add (NULL allowed; returns default height)
**Returns:**
* uint32_t Height in pixels
gfx_disp_is_flushing_last()
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Check if display is currently flushing the last block
.. code-block:: c
bool gfx_disp_is_flushing_last(gfx_disp_t *disp);
**Parameters:**
* ``disp`` - Display from gfx_disp_add
**Returns:**
* true if flushing last block, false otherwise
gfx_disp_get_perf_stats()
~~~~~~~~~~~~~~~~~~~~~~~~~
Get latest per-display performance statistics
.. code-block:: c
esp_err_t gfx_disp_get_perf_stats(gfx_disp_t *disp, gfx_disp_perf_stats_t *out_stats);
**Parameters:**
* ``disp`` - Display handle
* ``out_stats`` - Output stats structure
**Returns:**
* ESP_OK on success
gfx_disp_set_bg_color()
~~~~~~~~~~~~~~~~~~~~~~~
Set default background color for a display
.. code-block:: c
esp_err_t gfx_disp_set_bg_color(gfx_disp_t *disp, gfx_color_t color);
**Parameters:**
* ``disp`` - Display from gfx_disp_add
* ``color`` - Background color (e.g. RGB565)
**Returns:**
* esp_err_t ESP_OK on success
gfx_disp_set_bg_enable()
~~~~~~~~~~~~~~~~~~~~~~~~
Enable or disable drawing the background (fill with bg_color before widgets)
.. code-block:: c
esp_err_t gfx_disp_set_bg_enable(gfx_disp_t *disp, bool enable);
**Parameters:**
* ``disp`` - Display from gfx_disp_add
* ``enable`` - true to enable background (default), false to disable background
**Returns:**
* ESP_OK on success

View File

@ -0,0 +1,58 @@
Log (gfx_log)
=============
Types
-----
gfx_log_level_t
~~~~~~~~~~~~~~~
.. code-block:: c
typedef enum {
GFX_LOG_LEVEL_NONE = 0,
GFX_LOG_LEVEL_ERROR,
GFX_LOG_LEVEL_WARN,
GFX_LOG_LEVEL_INFO,
GFX_LOG_LEVEL_DEBUG,
GFX_LOG_LEVEL_VERBOSE,
} gfx_log_level_t;
gfx_log_module_t
~~~~~~~~~~~~~~~~
.. code-block:: c
typedef enum {
GFX_LOG_MODULE_CORE = 0,
GFX_LOG_MODULE_DISP,
GFX_LOG_MODULE_OBJ,
GFX_LOG_MODULE_REFR,
GFX_LOG_MODULE_RENDER,
GFX_LOG_MODULE_TIMER,
GFX_LOG_MODULE_TOUCH,
GFX_LOG_MODULE_IMG_DEC,
GFX_LOG_MODULE_LABEL,
GFX_LOG_MODULE_LABEL_OBJ,
GFX_LOG_MODULE_DRAW_LABEL,
GFX_LOG_MODULE_FONT_LV,
GFX_LOG_MODULE_FONT_FT,
GFX_LOG_MODULE_IMG,
GFX_LOG_MODULE_QRCODE,
GFX_LOG_MODULE_BUTTON,
GFX_LOG_MODULE_ANIM,
GFX_LOG_MODULE_ANIM_DEC,
GFX_LOG_MODULE_EAF_DEC,
GFX_LOG_MODULE_QRCODE_LIB,
GFX_LOG_MODULE_COUNT,
} gfx_log_module_t;
Functions
---------
gfx_log_set_level()
~~~~~~~~~~~~~~~~~~~
.. code-block:: c
void gfx_log_set_level(gfx_log_module_t module, gfx_log_level_t level);

View File

@ -0,0 +1,203 @@
Object (gfx_obj)
================
Types
-----
gfx_obj_touch_cb_t
~~~~~~~~~~~~~~~~~~
Application-level touch callback (register with gfx_obj_set_touch_cb)
.. code-block:: c
typedef void (*gfx_obj_touch_cb_t)(gfx_obj_t *obj, const gfx_touch_event_t *event, void *user_data);
Functions
---------
gfx_obj_set_pos()
~~~~~~~~~~~~~~~~~
.. code-block:: c
esp_err_t gfx_obj_set_pos(gfx_obj_t *obj, gfx_coord_t x, gfx_coord_t y);
gfx_obj_set_size()
~~~~~~~~~~~~~~~~~~
Set the size of an object
.. code-block:: c
esp_err_t gfx_obj_set_size(gfx_obj_t *obj, uint16_t w, uint16_t h);
**Parameters:**
* ``obj`` - Pointer to the object
* ``w`` - Width
* ``h`` - Height
gfx_obj_align()
~~~~~~~~~~~~~~~
Align an object relative to the screen or another object
.. code-block:: c
esp_err_t gfx_obj_align(gfx_obj_t *obj, uint8_t align, gfx_coord_t x_ofs, gfx_coord_t y_ofs);
**Parameters:**
* ``obj`` - Pointer to the object to align
* ``align`` - Alignment type (see GFX_ALIGN_* constants)
* ``x_ofs`` - X offset from the alignment position
* ``y_ofs`` - Y offset from the alignment position
gfx_obj_align_to()
~~~~~~~~~~~~~~~~~~
Align an object relative to another object
.. code-block:: c
esp_err_t gfx_obj_align_to(gfx_obj_t *obj, gfx_obj_t *base, uint8_t align, gfx_coord_t x_ofs, gfx_coord_t y_ofs);
**Parameters:**
* ``obj`` - Pointer to the object to align
* ``base`` - Reference object; NULL means align to the display
* ``align`` - Alignment type (see GFX_ALIGN_* constants)
* ``x_ofs`` - X offset from the alignment position
* ``y_ofs`` - Y offset from the alignment position
**Returns:**
* ESP_OK on success
gfx_obj_set_visible()
~~~~~~~~~~~~~~~~~~~~~
Set object visibility
.. code-block:: c
esp_err_t gfx_obj_set_visible(gfx_obj_t *obj, bool visible);
**Parameters:**
* ``obj`` - Object to set visibility for
* ``visible`` - True to make object visible, false to hide
gfx_obj_get_visible()
~~~~~~~~~~~~~~~~~~~~~
Get object visibility
.. code-block:: c
bool gfx_obj_get_visible(gfx_obj_t *obj);
**Parameters:**
* ``obj`` - Object to check visibility for
**Returns:**
* True if object is visible, false if hidden
gfx_obj_update_layout()
~~~~~~~~~~~~~~~~~~~~~~~
Update object's layout (mark for recalculation before rendering)
.. code-block:: c
void gfx_obj_update_layout(gfx_obj_t *obj);
**Parameters:**
* ``obj`` - Object to update layout
**Note:**
This is used when object properties that affect layout have changed, but the actual position calculation needs to be deferred until rendering
gfx_obj_get_pos()
~~~~~~~~~~~~~~~~~
Get the position of an object
.. code-block:: c
esp_err_t gfx_obj_get_pos(gfx_obj_t *obj, gfx_coord_t *x, gfx_coord_t *y);
**Parameters:**
* ``obj`` - Pointer to the object
* ``x`` - Pointer to store X coordinate
* ``y`` - Pointer to store Y coordinate
gfx_obj_get_size()
~~~~~~~~~~~~~~~~~~
Get the size of an object
.. code-block:: c
esp_err_t gfx_obj_get_size(gfx_obj_t *obj, uint16_t *w, uint16_t *h);
**Parameters:**
* ``obj`` - Pointer to the object
* ``w`` - Pointer to store width
* ``h`` - Pointer to store height
gfx_obj_delete()
~~~~~~~~~~~~~~~~
Delete an object
.. code-block:: c
esp_err_t gfx_obj_delete(gfx_obj_t *obj);
**Parameters:**
* ``obj`` - Pointer to the object to delete
gfx_obj_set_touch_cb()
~~~~~~~~~~~~~~~~~~~~~~
Register application touch callback for an object
.. code-block:: c
esp_err_t gfx_obj_set_touch_cb(gfx_obj_t *obj, gfx_obj_touch_cb_t cb, void *user_data);
**Parameters:**
* ``obj`` - Object to listen on
* ``cb`` - Callback (NULL to clear)
* ``user_data`` - Passed to cb
**Returns:**
* ESP_OK on success
gfx_obj_get_trace_id()
~~~~~~~~~~~~~~~~~~~~~~
Get object creation sequence id (monotonic per process lifetime)
.. code-block:: c
uint32_t gfx_obj_get_trace_id(gfx_obj_t *obj);
**Parameters:**
* ``obj`` - Object pointer
**Returns:**
* uint32_t Sequence id, 0 if obj is NULL

View File

@ -0,0 +1,157 @@
Timer (gfx_timer)
=================
Types
-----
gfx_timer_handle_t
~~~~~~~~~~~~~~~~~~
.. code-block:: c
typedef void *gfx_timer_handle_t;
gfx_timer_cb_t
~~~~~~~~~~~~~~
.. code-block:: c
typedef void (*gfx_timer_cb_t)(void *);
Functions
---------
gfx_timer_create()
~~~~~~~~~~~~~~~~~~
.. code-block:: c
gfx_timer_handle_t gfx_timer_create(void *handle, gfx_timer_cb_t timer_cb, uint32_t period, void *user_data);
gfx_timer_delete()
~~~~~~~~~~~~~~~~~~
Delete a timer
.. code-block:: c
void gfx_timer_delete(void *handle, gfx_timer_handle_t timer);
**Parameters:**
* ``handle`` - Player handle
* ``timer`` - Timer handle to delete
gfx_timer_pause()
~~~~~~~~~~~~~~~~~
Pause a timer
.. code-block:: c
void gfx_timer_pause(gfx_timer_handle_t timer);
**Parameters:**
* ``timer`` - Timer handle to pause
gfx_timer_resume()
~~~~~~~~~~~~~~~~~~
Resume a timer
.. code-block:: c
void gfx_timer_resume(gfx_timer_handle_t timer);
**Parameters:**
* ``timer`` - Timer handle to resume
gfx_timer_is_running()
~~~~~~~~~~~~~~~~~~~~~~
Check if a timer is running
.. code-block:: c
bool gfx_timer_is_running(gfx_timer_handle_t timer_handle);
**Parameters:**
* ``timer_handle`` - Timer handle to check
**Returns:**
* true if timer is running, false otherwise
gfx_timer_set_repeat_count()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Set timer repeat count
.. code-block:: c
void gfx_timer_set_repeat_count(gfx_timer_handle_t timer, int32_t repeat_count);
**Parameters:**
* ``timer`` - Timer handle to modify
* ``repeat_count`` - Number of times to repeat (-1 for infinite)
gfx_timer_set_period()
~~~~~~~~~~~~~~~~~~~~~~
Set timer period
.. code-block:: c
void gfx_timer_set_period(gfx_timer_handle_t timer, uint32_t period);
**Parameters:**
* ``timer`` - Timer handle to modify
* ``period`` - New period in milliseconds
gfx_timer_reset()
~~~~~~~~~~~~~~~~~
Reset a timer
.. code-block:: c
void gfx_timer_reset(gfx_timer_handle_t timer);
**Parameters:**
* ``timer`` - Timer handle to reset
gfx_timer_tick_get()
~~~~~~~~~~~~~~~~~~~~
Get current system tick
.. code-block:: c
uint32_t gfx_timer_tick_get(void);
**Returns:**
* Current tick value in milliseconds
gfx_timer_get_actual_fps()
~~~~~~~~~~~~~~~~~~~~~~~~~~
Get actual FPS from timer manager
.. code-block:: c
uint32_t gfx_timer_get_actual_fps(void *handle);
**Parameters:**
* ``handle`` - Player handle
**Returns:**
* Actual FPS value, 0 if handle is invalid

View File

@ -0,0 +1,77 @@
Touch (gfx_touch)
=================
Types
-----
gfx_touch_event_cb_t
~~~~~~~~~~~~~~~~~~~~
.. code-block:: c
typedef void (*gfx_touch_event_cb_t)(gfx_touch_t *touch, const gfx_touch_event_t *event, void *user_data);
gfx_touch_event_type_t
~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: c
typedef enum {
GFX_TOUCH_EVENT_PRESS = 0,
GFX_TOUCH_EVENT_RELEASE,
GFX_TOUCH_EVENT_MOVE, /**< Finger moved while pressed (slide) */
} gfx_touch_event_type_t;
gfx_touch_config_t
~~~~~~~~~~~~~~~~~~
.. code-block:: c
typedef struct {
esp_lcd_touch_handle_t handle; /**< LCD touch driver handle */
gfx_touch_event_cb_t event_cb; /**< Event callback */
uint32_t poll_ms; /**< Poll interval ms (0 = default) */
gfx_disp_t *disp; /**< Display handle */
void *user_data; /**< User data for callback */
} gfx_touch_config_t;
Functions
---------
gfx_touch_add()
~~~~~~~~~~~~~~~
.. code-block:: c
gfx_touch_t * gfx_touch_add(gfx_handle_t handle, const gfx_touch_config_t *cfg);
gfx_touch_set_disp()
~~~~~~~~~~~~~~~~~~~~
Bind a display to a touch device
.. code-block:: c
esp_err_t gfx_touch_set_disp(gfx_touch_t *touch, gfx_disp_t *disp);
**Parameters:**
* ``touch`` - Touch pointer returned from gfx_touch_add
* ``disp`` - Display to receive touch hit-testing and dispatch
**Returns:**
* ESP_OK on success, ESP_ERR_INVALID_ARG if touch is NULL
gfx_touch_del()
~~~~~~~~~~~~~~~
Remove a touch device from the list and release resources (stops polling, disables IRQ). Does not free the gfx_touch_t; caller must free(touch) after.
.. code-block:: c
void gfx_touch_del(gfx_touch_t *touch);
**Parameters:**
* ``touch`` - Touch pointer returned from gfx_touch_add; safe to pass NULL

View File

@ -0,0 +1,85 @@
Types (gfx_types)
=================
Types
-----
gfx_opa_t
~~~~~~~~~
.. code-block:: c
typedef uint8_t gfx_opa_t;
gfx_coord_t
~~~~~~~~~~~
.. code-block:: c
typedef int16_t gfx_coord_t;
gfx_handle_t
~~~~~~~~~~~~
.. code-block:: c
typedef void *gfx_handle_t;
gfx_area_t
~~~~~~~~~~
.. code-block:: c
typedef struct {
gfx_coord_t x1;
gfx_coord_t y1;
gfx_coord_t x2;
gfx_coord_t y2;
} gfx_area_t;
Macros
------
GFX_BUFFER_OFFSET_16BPP()
~~~~~~~~~~~~~~~~~~~~~~~~~
Calculate buffer pointer with offset for 16-bit format (RGB565)
.. code-block:: c
#define GFX_BUFFER_OFFSET_16BPP(buffer, y_offset, stride, x_offset) \
GFX_BUFFER_OFFSET_8BPP()
~~~~~~~~~~~~~~~~~~~~~~~~
Calculate buffer pointer with offset for 8-bit format
.. code-block:: c
#define GFX_BUFFER_OFFSET_8BPP(buffer, y_offset, stride, x_offset) \
GFX_BUFFER_OFFSET_4BPP()
~~~~~~~~~~~~~~~~~~~~~~~~
Calculate buffer pointer with offset for 4-bit format (2 pixels per byte)
.. code-block:: c
#define GFX_BUFFER_OFFSET_4BPP(buffer, y_offset, stride, x_offset) \
GFX_COLOR_HEX()
~~~~~~~~~~~~~~~
.. code-block:: c
#define GFX_COLOR_HEX(color) ((gfx_color_t)gfx_color_hex(color))
Functions
---------
gfx_color_hex()
~~~~~~~~~~~~~~~
.. code-block:: c
gfx_color_t gfx_color_hex(uint32_t c);

View File

@ -0,0 +1,26 @@
Core API Reference
==================
The core API provides the foundation for the graphics framework, including initialization, object management, and basic types.
.. toctree::
:maxdepth: 2
gfx_core
gfx_disp
gfx_log
gfx_obj
gfx_timer
gfx_touch
gfx_types
Core Modules
------------
* :doc:`gfx_core` - Core System (gfx_core)
* :doc:`gfx_disp` - Display (gfx_disp)
* :doc:`gfx_log` - Log (gfx_log)
* :doc:`gfx_obj` - Object (gfx_obj)
* :doc:`gfx_timer` - Timer (gfx_timer)
* :doc:`gfx_touch` - Touch (gfx_touch)
* :doc:`gfx_types` - Types (gfx_types)

View File

@ -0,0 +1,229 @@
Animation (gfx_anim)
====================
Types
-----
gfx_anim_segment_action_t
~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: c
typedef enum {
GFX_ANIM_SEGMENT_ACTION_CONTINUE = 0,
GFX_ANIM_SEGMENT_ACTION_PAUSE,
} gfx_anim_segment_action_t;
gfx_anim_src_type_t
~~~~~~~~~~~~~~~~~~~
Public animation source type.
.. code-block:: c
typedef enum {
GFX_ANIM_SRC_TYPE_MEMORY = 0, /**< In-memory animation payload */
} gfx_anim_src_type_t;
gfx_anim_segment_t
~~~~~~~~~~~~~~~~~~
Playback description for one animation segment.
.. code-block:: c
typedef struct {
uint32_t start; /* inclusive start frame */
uint32_t end; /* inclusive end frame */
uint32_t fps; /* playback fps for this segment */
uint32_t play_count; /* total plays for this segment, 0 means forever */
gfx_anim_segment_action_t end_action; /* action after the last play finishes */
} gfx_anim_segment_t;
gfx_anim_src_t
~~~~~~~~~~~~~~
Typed animation source descriptor.
.. code-block:: c
typedef struct {
gfx_anim_src_type_t type; /**< Source payload type */
const void *data; /**< Type-specific payload pointer */
size_t data_len; /**< Payload length in bytes */
} gfx_anim_src_t;
Functions
---------
gfx_anim_create()
~~~~~~~~~~~~~~~~~
.. code-block:: c
gfx_obj_t * gfx_anim_create(gfx_disp_t *disp);
gfx_anim_set_src_desc()
~~~~~~~~~~~~~~~~~~~~~~~
Set the typed source descriptor for an animation object
.. code-block:: c
esp_err_t gfx_anim_set_src_desc(gfx_obj_t *obj, const gfx_anim_src_t *src);
**Parameters:**
* ``obj`` - Pointer to the animation object
* ``src`` - Pointer to the typed source descriptor
**Returns:**
* ESP_OK on success, error code otherwise
gfx_anim_set_src()
~~~~~~~~~~~~~~~~~~
Set the source data for an animation object
.. code-block:: c
esp_err_t gfx_anim_set_src(gfx_obj_t *obj, const void *src_data, size_t src_len);
**Parameters:**
* ``obj`` - Pointer to the animation object
* ``src_data`` - Source data
* ``src_len`` - Source data length
**Returns:**
* ESP_OK on success, error code otherwise
gfx_anim_set_segment()
~~~~~~~~~~~~~~~~~~~~~~
Set the segment for an animation object
.. code-block:: c
esp_err_t gfx_anim_set_segment(gfx_obj_t *obj, uint32_t start, uint32_t end, uint32_t fps, bool repeat);
**Parameters:**
* ``obj`` - Pointer to the animation object
* ``start`` - Start frame index
* ``end`` - End frame index
* ``fps`` - Frames per second
* ``repeat`` - Whether to repeat the animation
**Returns:**
* ESP_OK on success, error code otherwise
gfx_anim_set_segments()
~~~~~~~~~~~~~~~~~~~~~~~
Set a segment playback plan for an animation object
.. code-block:: c
esp_err_t gfx_anim_set_segments(gfx_obj_t *obj, const gfx_anim_segment_t *segments, size_t segment_count);
**Parameters:**
* ``obj`` - Pointer to the animation object
* ``segments`` - Segment plan array
* ``segment_count`` - Number of segment entries in the array
**Returns:**
* ESP_OK on success, error code otherwise
gfx_anim_play_left_to_tail()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Drain the remaining segment plan and block until playback finishes
.. code-block:: c
esp_err_t gfx_anim_play_left_to_tail(gfx_obj_t *obj);
**Parameters:**
* ``obj`` - Pointer to the animation object
**Returns:**
* ESP_OK on success, ESP_ERR_NOT_FOUND if there is no remaining work, or another ESP_ERR_* code on failure
gfx_anim_start()
~~~~~~~~~~~~~~~~
Start the animation
.. code-block:: c
esp_err_t gfx_anim_start(gfx_obj_t *obj);
**Parameters:**
* ``obj`` - Pointer to the animation object
**Returns:**
* ESP_OK on success, error code otherwise
gfx_anim_stop()
~~~~~~~~~~~~~~~
Stop the animation
.. code-block:: c
esp_err_t gfx_anim_stop(gfx_obj_t *obj);
**Parameters:**
* ``obj`` - Pointer to the animation object
**Returns:**
* ESP_OK on success, error code otherwise
gfx_anim_set_mirror()
~~~~~~~~~~~~~~~~~~~~~
Set mirror display for an animation object
.. code-block:: c
esp_err_t gfx_anim_set_mirror(gfx_obj_t *obj, bool enabled, int16_t offset);
**Parameters:**
* ``obj`` - Pointer to the animation object
* ``enabled`` - Whether to enable mirror display
* ``offset`` - Mirror offset in pixels
**Returns:**
* ESP_OK on success, error code otherwise
gfx_anim_set_auto_mirror()
~~~~~~~~~~~~~~~~~~~~~~~~~~
Set auto mirror alignment for animation object
.. code-block:: c
esp_err_t gfx_anim_set_auto_mirror(gfx_obj_t *obj, bool enabled);
**Parameters:**
* ``obj`` - Animation object
* ``enabled`` - Whether to enable auto mirror alignment
**Returns:**
* ESP_OK on success, ESP_ERR_* otherwise

View File

@ -0,0 +1,184 @@
Button (gfx_button)
===================
Functions
---------
gfx_button_create()
~~~~~~~~~~~~~~~~~~~
Create a button object on a display
.. code-block:: c
gfx_obj_t * gfx_button_create(gfx_disp_t *disp);
**Parameters:**
* ``disp`` - Display from gfx_disp_add()
**Returns:**
* Pointer to the created button object
gfx_button_set_text()
~~~~~~~~~~~~~~~~~~~~~
Set the label text for a button
.. code-block:: c
esp_err_t gfx_button_set_text(gfx_obj_t *obj, const char *text);
**Parameters:**
* ``obj`` - Button object
* ``text`` - Text string; NULL is treated as an empty string
**Returns:**
* ESP_OK on success, error code otherwise
gfx_button_set_text_fmt()
~~~~~~~~~~~~~~~~~~~~~~~~~
Set the label text for a button using printf-style formatting
.. code-block:: c
esp_err_t gfx_button_set_text_fmt(gfx_obj_t *obj, const char *fmt, ...);
**Parameters:**
* ``obj`` - Button object
* ``fmt`` - Format string
**Returns:**
* ESP_OK on success, error code otherwise
gfx_button_set_font()
~~~~~~~~~~~~~~~~~~~~~
Set the font used by the button label
.. code-block:: c
esp_err_t gfx_button_set_font(gfx_obj_t *obj, gfx_font_t font);
**Parameters:**
* ``obj`` - Button object
* ``font`` - Font handle
**Returns:**
* ESP_OK on success, error code otherwise
gfx_button_set_text_color()
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Set the label text color for a button
.. code-block:: c
esp_err_t gfx_button_set_text_color(gfx_obj_t *obj, gfx_color_t color);
**Parameters:**
* ``obj`` - Button object
* ``color`` - Text color
**Returns:**
* ESP_OK on success, error code otherwise
gfx_button_set_bg_color()
~~~~~~~~~~~~~~~~~~~~~~~~~
Set the normal background color for a button
.. code-block:: c
esp_err_t gfx_button_set_bg_color(gfx_obj_t *obj, gfx_color_t color);
**Parameters:**
* ``obj`` - Button object
* ``color`` - Background color
**Returns:**
* ESP_OK on success, error code otherwise
gfx_button_set_bg_color_pressed()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Set the pressed background color for a button
.. code-block:: c
esp_err_t gfx_button_set_bg_color_pressed(gfx_obj_t *obj, gfx_color_t color);
**Parameters:**
* ``obj`` - Button object
* ``color`` - Pressed background color
**Returns:**
* ESP_OK on success, error code otherwise
gfx_button_set_border_color()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Set the border color for a button
.. code-block:: c
esp_err_t gfx_button_set_border_color(gfx_obj_t *obj, gfx_color_t color);
**Parameters:**
* ``obj`` - Button object
* ``color`` - Border color
**Returns:**
* ESP_OK on success, error code otherwise
gfx_button_set_border_width()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Set the border width for a button
.. code-block:: c
esp_err_t gfx_button_set_border_width(gfx_obj_t *obj, uint16_t width);
**Parameters:**
* ``obj`` - Button object
* ``width`` - Border width in pixels; 0 disables the border
**Returns:**
* ESP_OK on success, error code otherwise
gfx_button_set_text_align()
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Set the text alignment for a button label
.. code-block:: c
esp_err_t gfx_button_set_text_align(gfx_obj_t *obj, gfx_text_align_t align);
**Parameters:**
* ``obj`` - Button object
* ``align`` - Text alignment
**Returns:**
* ESP_OK on success, error code otherwise

View File

@ -0,0 +1,25 @@
LVGL Font Compatibility (gfx_font_lvgl)
=======================================
Functions
---------
gfx_font_lv_load_from_binary()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: c
lv_font_t * gfx_font_lv_load_from_binary(uint8_t *bin_addr);
gfx_font_lv_delete()
~~~~~~~~~~~~~~~~~~~~
Delete an LVGL font created from binary data
.. code-block:: c
void gfx_font_lv_delete(lv_font_t *font);
**Parameters:**
* ``font`` - Pointer to lv_font_t to delete

View File

@ -0,0 +1,112 @@
Image (gfx_img)
===============
Types
-----
gfx_color_format_t
~~~~~~~~~~~~~~~~~~
.. code-block:: c
typedef enum {
GFX_COLOR_FORMAT_RGB565 = 0x04, /**< RGB565 format without alpha channel */
GFX_COLOR_FORMAT_RGB565A8 = 0x0A, /**< RGB565 format with separate alpha channel */
} gfx_color_format_t;
gfx_img_src_type_t
~~~~~~~~~~~~~~~~~~
Public image source type.
.. code-block:: c
typedef enum {
GFX_IMG_SRC_TYPE_IMAGE_DSC = 0, /**< In-memory gfx_image_dsc_t payload */
} gfx_img_src_type_t;
gfx_image_header_t
~~~~~~~~~~~~~~~~~~
.. code-block:: c
typedef struct {
uint32_t magic: 8; /**< Magic number. Must be GFX_IMAGE_HEADER_MAGIC */
uint32_t cf : 8; /**< Color format: See `gfx_color_format_t` */
uint32_t flags: 16; /**< Image flags */
uint32_t w: 16; /**< Width of the image */
uint32_t h: 16; /**< Height of the image */
uint32_t stride: 16; /**< Number of bytes in a row */
uint32_t reserved: 16; /**< Reserved for future use */
} gfx_image_header_t;
gfx_image_dsc_t
~~~~~~~~~~~~~~~
.. code-block:: c
typedef struct {
gfx_image_header_t header; /**< A header describing the basics of the image */
uint32_t data_size; /**< Size of the image in bytes */
const uint8_t *data; /**< Pointer to the data of the image */
const void *reserved; /**< Reserved field for future use */
const void *reserved_2; /**< Reserved field for future use */
} gfx_image_dsc_t;
gfx_img_src_t
~~~~~~~~~~~~~
Typed image source descriptor.
.. code-block:: c
typedef struct {
gfx_img_src_type_t type; /**< Source payload type */
const void *data; /**< Type-specific payload pointer */
} gfx_img_src_t;
Functions
---------
gfx_img_create()
~~~~~~~~~~~~~~~~
.. code-block:: c
gfx_obj_t * gfx_img_create(gfx_disp_t *disp);
gfx_img_set_src_desc()
~~~~~~~~~~~~~~~~~~~~~~
Set the typed source descriptor for an image object
.. code-block:: c
esp_err_t gfx_img_set_src_desc(gfx_obj_t *obj, const gfx_img_src_t *src);
**Parameters:**
* ``obj`` - Pointer to the image object
* ``src`` - Pointer to the typed source descriptor
**Returns:**
* ESP_OK on success, ESP_ERR_* otherwise
gfx_img_set_src()
~~~~~~~~~~~~~~~~~
Set the source data for an image object
.. code-block:: c
esp_err_t gfx_img_set_src(gfx_obj_t *obj, void *src);
**Parameters:**
* ``obj`` - Pointer to the image object
* ``src`` - Pointer to the image source data
**Returns:**
* ESP_OK on success, ESP_ERR_* otherwise

View File

@ -0,0 +1,387 @@
Label (gfx_label)
=================
Types
-----
gfx_font_t
~~~~~~~~~~
.. code-block:: c
typedef void *gfx_font_t;
gfx_text_align_t
~~~~~~~~~~~~~~~~
.. code-block:: c
typedef enum {
GFX_TEXT_ALIGN_AUTO, /**< Align text auto */
GFX_TEXT_ALIGN_LEFT, /**< Align text to left */
GFX_TEXT_ALIGN_CENTER, /**< Align text to center */
GFX_TEXT_ALIGN_RIGHT, /**< Align text to right */
} gfx_text_align_t;
gfx_label_long_mode_t
~~~~~~~~~~~~~~~~~~~~~
.. code-block:: c
typedef enum {
GFX_LABEL_LONG_WRAP, /**< Break the long lines (word wrap) */
GFX_LABEL_LONG_SCROLL, /**< Make the text scrolling horizontally smoothly */
GFX_LABEL_LONG_CLIP, /**< Simply clip the parts which don't fit */
GFX_LABEL_LONG_SCROLL_SNAP, /**< Jump to next section after interval (horizontal paging) */
} gfx_label_long_mode_t;
gfx_label_cfg_t
~~~~~~~~~~~~~~~
.. code-block:: c
typedef struct {
const char *name; /**< The name of the font file */
const void *mem; /**< The pointer to the font file */
size_t mem_size; /**< The size of the memory */
uint16_t font_size; /**< The size of the font */
} gfx_label_cfg_t;
Functions
---------
gfx_label_create()
~~~~~~~~~~~~~~~~~~
.. code-block:: c
gfx_obj_t * gfx_label_create(gfx_disp_t *disp);
gfx_label_new_font()
~~~~~~~~~~~~~~~~~~~~
Create a new font
.. code-block:: c
esp_err_t gfx_label_new_font(const gfx_label_cfg_t *cfg, gfx_font_t *ret_font);
**Parameters:**
* ``cfg`` - Font configuration
* ``ret_font`` - Pointer to store the font handle
**Returns:**
* ESP_OK on success, error code otherwise
gfx_label_delete_font()
~~~~~~~~~~~~~~~~~~~~~~~
Delete a font and free its resources
.. code-block:: c
esp_err_t gfx_label_delete_font(gfx_font_t font);
**Parameters:**
* ``font`` - Font handle to delete
**Returns:**
* ESP_OK on success, error code otherwise
gfx_label_set_text()
~~~~~~~~~~~~~~~~~~~~
Set the text for a label object
.. code-block:: c
esp_err_t gfx_label_set_text(gfx_obj_t *obj, const char *text);
**Parameters:**
* ``obj`` - Pointer to the label object
* ``text`` - Text string to display
**Returns:**
* ESP_OK on success, error code otherwise
gfx_label_set_text_fmt()
~~~~~~~~~~~~~~~~~~~~~~~~
Set the text for a label object with format
.. code-block:: c
esp_err_t gfx_label_set_text_fmt(gfx_obj_t *obj, const char *fmt, ...);
**Parameters:**
* ``obj`` - Pointer to the label object
* ``fmt`` - Format string
**Returns:**
* ESP_OK on success, error code otherwise
gfx_label_set_color()
~~~~~~~~~~~~~~~~~~~~~
Set the color for a label object
.. code-block:: c
esp_err_t gfx_label_set_color(gfx_obj_t *obj, gfx_color_t color);
**Parameters:**
* ``obj`` - Pointer to the label object
* ``color`` - Color value
**Returns:**
* ESP_OK on success, error code otherwise
gfx_label_set_bg_color()
~~~~~~~~~~~~~~~~~~~~~~~~
Set the background color for a label object
.. code-block:: c
esp_err_t gfx_label_set_bg_color(gfx_obj_t *obj, gfx_color_t bg_color);
**Parameters:**
* ``obj`` - Pointer to the label object
* ``bg_color`` - Background color value
**Returns:**
* ESP_OK on success, error code otherwise
gfx_label_set_bg_enable()
~~~~~~~~~~~~~~~~~~~~~~~~~
Enable or disable background for a label object
.. code-block:: c
esp_err_t gfx_label_set_bg_enable(gfx_obj_t *obj, bool enable);
**Parameters:**
* ``obj`` - Pointer to the label object
* ``enable`` - True to enable background, false to disable
**Returns:**
* ESP_OK on success, error code otherwise
gfx_label_set_opa()
~~~~~~~~~~~~~~~~~~~
Set the opacity for a label object
.. code-block:: c
esp_err_t gfx_label_set_opa(gfx_obj_t *obj, gfx_opa_t opa);
**Parameters:**
* ``obj`` - Pointer to the label object
* ``opa`` - Opacity value (0-255)
**Returns:**
* ESP_OK on success, error code otherwise
gfx_label_set_font()
~~~~~~~~~~~~~~~~~~~~
Set the font for a label object
.. code-block:: c
esp_err_t gfx_label_set_font(gfx_obj_t *obj, gfx_font_t font);
**Parameters:**
* ``obj`` - Pointer to the label object
* ``font`` - Font handle
gfx_label_set_text_align()
~~~~~~~~~~~~~~~~~~~~~~~~~~
Set the text alignment for a label object
.. code-block:: c
esp_err_t gfx_label_set_text_align(gfx_obj_t *obj, gfx_text_align_t align);
**Parameters:**
* ``obj`` - Pointer to the label object
* ``align`` - Text alignment value
**Returns:**
* ESP_OK on success, error code otherwise
gfx_label_set_long_mode()
~~~~~~~~~~~~~~~~~~~~~~~~~
Set the long text mode for a label object
.. code-block:: c
esp_err_t gfx_label_set_long_mode(gfx_obj_t *obj, gfx_label_long_mode_t long_mode);
**Parameters:**
* ``obj`` - Pointer to the label object
* ``long_mode`` - Long text handling mode (wrap, scroll, or clip)
**Returns:**
* ESP_OK on success, error code otherwise
gfx_label_set_line_spacing()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Set the line spacing for a label object
.. code-block:: c
esp_err_t gfx_label_set_line_spacing(gfx_obj_t *obj, uint16_t spacing);
**Parameters:**
* ``obj`` - Pointer to the label object
* ``spacing`` - Line spacing in pixels
**Returns:**
* ESP_OK on success, error code otherwise
gfx_label_set_scroll_speed()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Set the horizontal scrolling speed for a label object
.. code-block:: c
esp_err_t gfx_label_set_scroll_speed(gfx_obj_t *obj, uint32_t speed_ms);
**Parameters:**
* ``obj`` - Pointer to the label object
* ``speed_ms`` - Scrolling speed in milliseconds per pixel
**Returns:**
* ESP_OK on success, error code otherwise
**Note:**
Only effective when long_mode is GFX_LABEL_LONG_SCROLL
gfx_label_set_scroll_loop()
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Set whether scrolling should loop continuously
.. code-block:: c
esp_err_t gfx_label_set_scroll_loop(gfx_obj_t *obj, bool loop);
**Parameters:**
* ``obj`` - Pointer to the label object
* ``loop`` - True to enable continuous looping, false for one-time scroll
**Returns:**
* ESP_OK on success, error code otherwise
**Note:**
Only effective when long_mode is GFX_LABEL_LONG_SCROLL
gfx_label_set_scroll_step()
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Set the scroll step size for a label object
.. code-block:: c
esp_err_t gfx_label_set_scroll_step(gfx_obj_t *obj, int32_t step);
**Parameters:**
* ``obj`` - Pointer to the label object
* ``step`` - Scroll step size in pixels per timer tick (default: 1, can be negative)
**Returns:**
* ESP_OK on success, error code otherwise
**Note:**
Only effective when long_mode is GFX_LABEL_LONG_SCROLL
**Note:**
Step cannot be zero
gfx_label_set_snap_interval()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Set the snap scroll interval time for a label object
.. code-block:: c
esp_err_t gfx_label_set_snap_interval(gfx_obj_t *obj, uint32_t interval_ms);
**Parameters:**
* ``obj`` - Pointer to the label object
* ``interval_ms`` - Interval time in milliseconds to stay on each section before jumping
**Returns:**
* ESP_OK on success, error code otherwise
**Note:**
Only effective when long_mode is GFX_LABEL_LONG_SCROLL_SNAP
**Note:**
The jump offset is automatically calculated as the label width
gfx_label_set_snap_loop()
~~~~~~~~~~~~~~~~~~~~~~~~~
Set whether snap scrolling should loop continuously
.. code-block:: c
esp_err_t gfx_label_set_snap_loop(gfx_obj_t *obj, bool loop);
**Parameters:**
* ``obj`` - Pointer to the label object
* ``loop`` - True to enable continuous looping, false to stop at end
**Returns:**
* ESP_OK on success, error code otherwise
**Note:**
Only effective when long_mode is GFX_LABEL_LONG_SCROLL_SNAP

View File

@ -0,0 +1,96 @@
Motion Driver (gfx_motion)
==========================
Types
-----
gfx_motion_cfg_t
~~~~~~~~~~~~~
.. code-block:: c
typedef struct gfx_motion_cfg_t {
uint16_t timer_period_ms;
int16_t damping_div;
} gfx_motion_cfg_t;
gfx_motion_tick_cb_t
~~~~~~~~~~~~~~~~~
Callback executed on each motion timer tick. Return ``true`` when state changed and an apply pass is needed.
.. code-block:: c
typedef bool (*gfx_motion_tick_cb_t)(gfx_motion_t *motion, void *user_data);
gfx_motion_apply_cb_t
~~~~~~~~~~~~~~~~~~
Callback that pushes the current motion state into display objects.
.. code-block:: c
typedef esp_err_t (*gfx_motion_apply_cb_t)(gfx_motion_t *motion, void *user_data, bool force_apply);
Functions
---------
gfx_motion_cfg_init()
~~~~~~~~~~~~~~~~~~
Initialize a motion config with timer period and damping divisor.
.. code-block:: c
void gfx_motion_cfg_init(gfx_motion_cfg_t *cfg, uint16_t timer_period_ms, int16_t damping_div);
gfx_motion_init()
~~~~~~~~~~~~~~
Create and start a motion driver bound to a display and anchor object.
.. code-block:: c
esp_err_t gfx_motion_init(gfx_motion_t *motion,
gfx_disp_t *disp,
gfx_obj_t *anchor,
const gfx_motion_cfg_t *cfg,
gfx_motion_tick_cb_t tick_cb,
gfx_motion_apply_cb_t apply_cb,
void *user_data);
gfx_motion_deinit()
~~~~~~~~~~~~~~~~
Stop and destroy the motion driver.
.. code-block:: c
void gfx_motion_deinit(gfx_motion_t *motion);
gfx_motion_set_period()
~~~~~~~~~~~~~~~~~~~~
Change the timer period of a running motion driver.
.. code-block:: c
esp_err_t gfx_motion_set_period(gfx_motion_t *motion, uint16_t period_ms);
gfx_motion_step()
~~~~~~~~~~~~~~
Run one motion tick immediately.
.. code-block:: c
esp_err_t gfx_motion_step(gfx_motion_t *motion, bool force_apply);
gfx_motion_ease_i16()
~~~~~~~~~~~~~~~~~~
Utility helper for damped integer interpolation.
.. code-block:: c
int16_t gfx_motion_ease_i16(int16_t cur, int16_t tgt, int16_t div);

View File

@ -0,0 +1,198 @@
Scene Asset and Runtime (gfx_motion_scene)
======================================
Overview
--------
``gfx_motion_scene.h`` defines both the ROM-side scene asset format and the runtime used to play it back on a display.
The asset model is built around:
* joints
* segments
* poses
* actions
* layout metadata
Important Types
---------------
gfx_motion_segment_kind_t
~~~~~~~~~~~~~~~~~
Segment primitive kind.
.. code-block:: c
typedef enum {
GFX_MOTION_SEG_CAPSULE = 0,
GFX_MOTION_SEG_RING = 1,
GFX_MOTION_SEG_BEZIER_STRIP = 2,
GFX_MOTION_SEG_BEZIER_LOOP = 3,
GFX_MOTION_SEG_BEZIER_FILL = 4,
} gfx_motion_segment_kind_t;
gfx_motion_segment_t
~~~~~~~~~~~~~~~~
One visual primitive wired to joints and optional style/resource metadata.
gfx_motion_pose_t
~~~~~~~~~~~~~
One pose containing a flat coordinate array for all joints.
gfx_motion_action_step_t and gfx_motion_action_t
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
These describe action playback: pose target, hold time, interpolation mode, facing, and loop behavior.
gfx_motion_asset_t
~~~~~~~~~~~~~~
Top-level scene asset bundle exported into ROM and consumed by the runtime.
gfx_motion_player_t
~~~~~~~~~~~~~~~~
Unified display runtime that owns:
* one ``gfx_motion_scene_t`` parser state
* one ``gfx_motion_t`` timer/runtime driver
* one ``gfx_mesh_img`` object per segment
Scene Functions
---------------
gfx_motion_scene_init()
~~~~~~~~~~~~~~~~~~~
Validate and initialize a parser scene state.
.. code-block:: c
esp_err_t gfx_motion_scene_init(gfx_motion_scene_t *scene, const gfx_motion_asset_t *asset);
gfx_motion_scene_set_action()
~~~~~~~~~~~~~~~~~~~~~~~
Switch the active action by index.
.. code-block:: c
esp_err_t gfx_motion_scene_set_action(gfx_motion_scene_t *scene, uint16_t action_index, bool snap_now);
gfx_motion_scene_set_action_loop()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Override the current action loop behavior.
.. code-block:: c
esp_err_t gfx_motion_scene_set_action_loop(gfx_motion_scene_t *scene, bool loop);
gfx_motion_scene_clear_action_loop_override()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Clear the loop override and restore the asset-defined loop flag.
.. code-block:: c
esp_err_t gfx_motion_scene_clear_action_loop_override(gfx_motion_scene_t *scene);
gfx_motion_scene_tick()
~~~~~~~~~~~~~~~~~~~
Advance the current pose toward its target pose.
.. code-block:: c
bool gfx_motion_scene_tick(gfx_motion_scene_t *scene);
gfx_motion_scene_advance()
~~~~~~~~~~~~~~~~~~~~~~
Advance the action timeline.
.. code-block:: c
void gfx_motion_scene_advance(gfx_motion_scene_t *scene);
Runtime Functions
-----------------
gfx_motion_player_init()
~~~~~~~~~~~~~~~~~~~~~
Create display objects for all scene segments and start the motion timer.
.. code-block:: c
esp_err_t gfx_motion_player_init(gfx_motion_player_t *player,
gfx_disp_t *disp,
const gfx_motion_asset_t *asset);
gfx_motion_player_deinit()
~~~~~~~~~~~~~~~~~~~~~~~
Destroy all segment objects and stop the runtime.
.. code-block:: c
void gfx_motion_player_deinit(gfx_motion_player_t *player);
gfx_motion_player_set_color()
~~~~~~~~~~~~~~~~~~~~~~~~~~
Set the default runtime color used by non-palette, non-textured segments.
.. code-block:: c
esp_err_t gfx_motion_player_set_color(gfx_motion_player_t *player, gfx_color_t color);
gfx_motion_player_set_canvas()
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Set the destination canvas rectangle the scene is scaled into.
.. code-block:: c
esp_err_t gfx_motion_player_set_canvas(gfx_motion_player_t *player,
gfx_coord_t x, gfx_coord_t y,
uint16_t w, uint16_t h);
gfx_motion_player_sync()
~~~~~~~~~~~~~~~~~~~~~~~~
Force the current player state to be pushed to display objects immediately without advancing the action timeline.
.. code-block:: c
esp_err_t gfx_motion_player_sync(gfx_motion_player_t *player);
gfx_motion_player_set_action()
~~~~~~~~~~~~~~~~~~~~~~~~~
Switch the active runtime action.
.. code-block:: c
esp_err_t gfx_motion_player_set_action(gfx_motion_player_t *player, uint16_t action_idx, bool snap);
gfx_motion_player_set_action_loop()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Override action loop behavior at runtime.
.. code-block:: c
esp_err_t gfx_motion_player_set_action_loop(gfx_motion_player_t *player, bool loop);
gfx_motion_player_clear_action_loop_override()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Clear the runtime loop override.
.. code-block:: c
esp_err_t gfx_motion_player_clear_action_loop_override(gfx_motion_player_t *player);

View File

@ -0,0 +1,121 @@
QR Code (gfx_qrcode)
====================
Types
-----
gfx_qrcode_ecc_t
~~~~~~~~~~~~~~~~
.. code-block:: c
typedef enum {
GFX_QRCODE_ECC_LOW = 0, /**< The QR Code can tolerate about 7% erroneous codewords */
GFX_QRCODE_ECC_MEDIUM, /**< The QR Code can tolerate about 15% erroneous codewords */
GFX_QRCODE_ECC_QUARTILE, /**< The QR Code can tolerate about 25% erroneous codewords */
GFX_QRCODE_ECC_HIGH /**< The QR Code can tolerate about 30% erroneous codewords */
} gfx_qrcode_ecc_t;
Functions
---------
gfx_qrcode_create()
~~~~~~~~~~~~~~~~~~~
.. code-block:: c
gfx_obj_t * gfx_qrcode_create(gfx_disp_t *disp);
gfx_qrcode_set_data()
~~~~~~~~~~~~~~~~~~~~~
Set the data/text for a QR Code object
.. code-block:: c
esp_err_t gfx_qrcode_set_data(gfx_obj_t *obj, const char *data);
**Parameters:**
* ``obj`` - Pointer to the QR Code object
* ``data`` - Pointer to the null-terminated string to encode
**Returns:**
* ESP_OK on success, error code otherwise
**Note:**
The length is automatically calculated using strlen()
gfx_qrcode_set_size()
~~~~~~~~~~~~~~~~~~~~~
Set the size for a QR Code object
.. code-block:: c
esp_err_t gfx_qrcode_set_size(gfx_obj_t *obj, uint16_t size);
**Parameters:**
* ``obj`` - Pointer to the QR Code object
* ``size`` - Size in pixels (both width and height)
**Returns:**
* ESP_OK on success, error code otherwise
gfx_qrcode_set_ecc()
~~~~~~~~~~~~~~~~~~~~
Set the error correction level for a QR Code object
.. code-block:: c
esp_err_t gfx_qrcode_set_ecc(gfx_obj_t *obj, gfx_qrcode_ecc_t ecc);
**Parameters:**
* ``obj`` - Pointer to the QR Code object
* ``ecc`` - Error correction level
**Returns:**
* ESP_OK on success, error code otherwise
gfx_qrcode_set_color()
~~~~~~~~~~~~~~~~~~~~~~
Set the foreground color for a QR Code object
.. code-block:: c
esp_err_t gfx_qrcode_set_color(gfx_obj_t *obj, gfx_color_t color);
**Parameters:**
* ``obj`` - Pointer to the QR Code object
* ``color`` - Foreground color (QR modules color)
**Returns:**
* ESP_OK on success, error code otherwise
gfx_qrcode_set_bg_color()
~~~~~~~~~~~~~~~~~~~~~~~~~
Set the background color for a QR Code object
.. code-block:: c
esp_err_t gfx_qrcode_set_bg_color(gfx_obj_t *obj, gfx_color_t bg_color);
**Parameters:**
* ``obj`` - Pointer to the QR Code object
* ``bg_color`` - Background color
**Returns:**
* ESP_OK on success, error code otherwise

View File

@ -0,0 +1,28 @@
Widget API Reference
====================
The widget API provides specialized functionality for different types of graphical elements.
.. toctree::
:maxdepth: 2
gfx_anim
gfx_button
gfx_font_lvgl
gfx_img
gfx_label
gfx_qrcode
gfx_motion
gfx_motion_scene
Widget Modules
--------------
* :doc:`gfx_anim` - Animation (gfx_anim)
* :doc:`gfx_button` - Button (gfx_button)
* :doc:`gfx_font_lvgl` - LVGL Font Compatibility (gfx_font_lvgl)
* :doc:`gfx_img` - Image (gfx_img)
* :doc:`gfx_label` - Label (gfx_label)
* :doc:`gfx_qrcode` - QR Code (gfx_qrcode)
* :doc:`gfx_motion` - Motion Driver (gfx_motion)
* :doc:`gfx_motion_scene` - Motion Scene and Player (gfx_motion_scene)

View File

@ -0,0 +1,109 @@
Changelog
=========
All notable changes to the ESP Emote GFX component will be documented in this file.
[3.0.5]
------------
* Add motion scene widget documentation covering ``gfx_motion``, ``gfx_motion_scene``, asset layout, and runtime usage
* Add motion widget example references to README and Sphinx docs
* Simplify the motion rendering path by removing NanoVG and libtess2 dependencies
* Keep polygon fill on the internal scanline fallback path for a leaner release footprint
[3.0.4] - 2026-04-21
--------------------
* restore ``gfx_disp_event_t``
* Render loop: sleep ``GFX_RENDER_TASK_IDLE_SLEEP_MS`` once before the main loop so the first frame is not driven until the caller can finish setup after ``add_disp()`` (avoids a startup deadlock)
[3.0.3] - 2026-04-20
--------------------
* Add `gfx_button` widget (text, font, normal/pressed colors, border)
* Add `gfx_log` API for log level configuration
* Documentation: separate English and Simplified Chinese HTML builds (gettext), language switcher, unified `postprocess_docs.sh` pipeline (API RST, Sphinx, Doxygen)
* Simplify GitHub Actions documentation job to a single build step
[3.0.2] - 2026-04-17
--------------------
* Update version of esp_new_jpeg
[3.0.1] - 2026-02-13
--------------------
* Add CI build action for P4
* Optimize multi-buffer switching logic
* Fix crash when text is NULL
* Fix missing API documentation (e.g. gfx_touch_add)
[3.0.0] - 2026-01-22
--------------------
* Add documentation build action
* Optimize EAF 8-bit render
* Fix FreeType parsing performance
* Remove duplicated label-related APIs
[2.1.0] - 2026-01-28
--------------------
* Support for decoding Heatshrink-compressed image slices
[2.0.4] - 2026-01-22
--------------------
* Fix Huffman+RLE decoding buffer sizing to prevent oversized output errors (Issue `#18 <https://github.com/espressif2022/esp_emote_gfx/issues/18>`_)
[2.0.3] - 2026-01-08
--------------------
* Delete local assets
* Build acion for ['release-v5.2', 'release-v5.3', 'release-v5.4', 'release-v5.5']
* Fix ESP-IDF version compatibility issues
* Change flush_callback timeout from 20 ms to wait forever
[2.0.2] - 2025-12-26
--------------------
* Add optional JPEG decoding support for EAF animations
* Center QR code rendering in UI layout
* Add alpha channel support for animations
[2.0.1] - 2025-12-05
--------------------
* Add Touch event
[2.0.0] - 2025-12-01
--------------------
* Added partial refresh mode support
* Added QR code widget (gfx_qrcode)
[1.2.0] - 2025-09-0
-------------------
* use eaf as a lib
[1.1.2] - 2025-09-29
--------------------
Upgrade dependencies
~~~~~~~~~~~~~~~~~~~~
* Update `espressif/esp_new_jpeg` to 0.6.x by @Kevincoooool. `#8 <https://github.com/espressif2022/esp_emote_gfx/pull/8>`_
[1.1.1] - 2025-09-23
--------------------
Fixed
~~~~~
* Resolve image block decoding failure in specific cases. `#6 <https://github.com/espressif2022/esp_emote_gfx/issues/6>`_
[1.0.0] - 2025-08-01
--------------------
Added
~~~~~
* Initial release of ESP Emote GFX framework
* Core graphics rendering engine
* Object system for images and labels
* Basic drawing functions and color utilities
* Software blending capabilities
* Timer system for animations
* Support for ESP-IDF 5.0+
* FreeType font rendering integration
* JPEG image decoding support
Features
~~~~~~~~
* Lightweight graphics framework optimized for embedded systems
* Memory-efficient design for resource-constrained environments

View File

@ -0,0 +1,76 @@
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
project = 'ESP Emote GFX'
copyright = '2024-2025, Espressif Systems (Shanghai) CO LTD'
author = 'Espressif Systems'
release = '1.0.0'
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.viewcode',
'sphinx.ext.intersphinx',
'breathe', # For Doxygen integration (optional)
]
templates_path = ['_templates']
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# gettext / Sphinx i18n: translations live in docs/locale/<lang>/LC_MESSAGES/*.mo
locale_dirs = ['locale']
gettext_compact = False
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
html_theme = 'sphinx_idf_theme'
html_static_path = ['_static']
html_logo = None
html_favicon = None
html_theme_options = {
'display_version': True,
}
html_css_files = ['esp_emote_gfx.css']
html_js_files = ['lang_switch.js']
project_slug = 'esp-emote-gfx'
project_homepage = 'https://github.com/espressif2022/esp_emote_gfx'
language = 'en'
languages = ['en', 'zh_CN']
idf_target = 'esp32'
idf_targets = ['esp32']
idf_target_title_dict = {
'esp32': 'ESP32',
}
versions_url = ''
pdf_file = ''
# -- Extension configuration -------------------------------------------------
# Breathe configuration (if using Doxygen)
breathe_projects = {
"esp_emote_gfx": "../doxygen/xml"
}
breathe_default_project = "esp_emote_gfx"
# Intersphinx mapping
intersphinx_mapping = {
'python': ('https://docs.python.org/3', None),
}
# -- Options for autodoc ----------------------------------------------------
autodoc_mock_imports = ['esp_err', 'esp_log', 'lvgl', 'freetype']
def setup(app):
app.add_config_value('pdf_file', pdf_file, 'html')
app.add_config_value('idf_target_title_dict', idf_target_title_dict, 'html')

View File

@ -0,0 +1,338 @@
Examples
========
This section provides comprehensive examples demonstrating various features of ESP Emote GFX.
Initialization (core + display + optional touch)
------------------------------------------------
Initialize the graphics core, add a display with flush callback, and optionally add touch. Widgets are created on the display (``gfx_disp_t *disp``).
.. code-block:: c
#include "gfx.h"
#include "esp_check.h"
#include "esp_log.h"
static const char *TAG = "gfx_app";
static gfx_handle_t gfx_handle = NULL;
static gfx_disp_t *gfx_disp = NULL;
static void disp_flush_callback(gfx_disp_t *disp, int x1, int y1, int x2, int y2, const void *data)
{
esp_lcd_panel_handle_t panel = (esp_lcd_panel_handle_t)gfx_disp_get_user_data(disp);
esp_lcd_panel_draw_bitmap(panel, x1, y1, x2, y2, data);
}
static void touch_event_cb(gfx_touch_t *touch, const gfx_touch_event_t *event, void *user_data)
{
ESP_LOGD(TAG, "touch type %d at (%d, %d)", event->type, event->x, event->y);
}
esp_err_t init_gfx(esp_lcd_panel_handle_t panel_handle, esp_lcd_touch_handle_t touch_handle)
{
esp_err_t ret = ESP_OK;
gfx_core_config_t gfx_cfg = {
.fps = 30,
.task = GFX_EMOTE_INIT_CONFIG(),
};
gfx_handle = gfx_emote_init(&gfx_cfg);
ESP_GOTO_ON_FALSE(gfx_handle != NULL, ESP_FAIL, err_out, TAG, "Failed to init GFX");
gfx_disp_config_t disp_cfg = {
.h_res = 320,
.v_res = 240,
.flush_cb = disp_flush_callback,
.update_cb = NULL,
.user_data = (void *)panel_handle,
.flags = { .swap = true },
.buffers = { .buf1 = NULL, .buf2 = NULL, .buf_pixels = 320 * 16 },
};
gfx_disp = gfx_disp_add(gfx_handle, &disp_cfg);
ESP_GOTO_ON_FALSE(gfx_disp != NULL, ESP_FAIL, err_gfx, TAG, "Failed to add display");
if (touch_handle) {
gfx_touch_config_t touch_cfg = {
.handle = touch_handle,
.event_cb = touch_event_cb,
.disp = gfx_disp,
.poll_ms = 50,
.user_data = NULL,
};
if (gfx_touch_add(gfx_handle, &touch_cfg) == NULL) {
ESP_LOGW(TAG, "Touch add failed");
}
}
return ESP_OK;
err_gfx:
gfx_emote_deinit(gfx_handle);
gfx_handle = NULL;
err_out:
return ret;
}
Basic Examples
--------------
Simple Label
~~~~~~~~~~~~
Create and display a simple text label on a display (``disp`` from ``gfx_disp_add``):
.. code-block:: c
#include "gfx.h"
void setup_label(gfx_disp_t *disp)
{
gfx_obj_t *label = gfx_label_create(disp);
gfx_label_set_text(label, "Hello, World!");
gfx_obj_set_pos(label, 50, 50);
gfx_label_set_color(label, GFX_COLOR_HEX(0xFF0000));
gfx_disp_refresh_all(disp);
}
Image Display
~~~~~~~~~~~~~
Display an image:
.. code-block:: c
#include "gfx.h"
void setup_image(gfx_disp_t *disp)
{
gfx_obj_t *img = gfx_img_create(disp);
extern const gfx_image_dsc_t my_image;
gfx_img_set_src(img, (void *)&my_image);
gfx_obj_align(img, GFX_ALIGN_CENTER, 0, 0);
}
Advanced Examples
-----------------
Multiple Widgets
~~~~~~~~~~~~~~~~
Create and manage multiple widgets on the same display:
.. code-block:: c
#include "gfx.h"
void setup_widgets(gfx_disp_t *disp)
{
gfx_obj_t *label = gfx_label_create(disp);
gfx_label_set_text(label, "Status: OK");
gfx_obj_set_pos(label, 10, 10);
gfx_obj_t *img = gfx_img_create(disp);
extern const gfx_image_dsc_t icon;
gfx_img_set_src(img, (void *)&icon);
gfx_obj_set_pos(img, 10, 50);
gfx_obj_t *anim = gfx_anim_create(disp);
gfx_anim_set_src(anim, anim_data, anim_size);
gfx_obj_set_size(anim, 100, 100);
gfx_obj_set_pos(anim, 150, 50);
gfx_anim_set_segment(anim, 0, 10, 30, true);
gfx_anim_start(anim);
}
Touch and object callback (e.g. drag)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Register a per-object touch callback so the object receives PRESS/MOVE/RELEASE (e.g. for dragging):
.. code-block:: c
#include "gfx.h"
static int32_t drag_off_x, drag_off_y;
static void obj_touch_cb(gfx_obj_t *obj, const gfx_touch_event_t *event, void *user_data)
{
gfx_coord_t ox, oy;
gfx_obj_get_pos(obj, &ox, &oy);
if (event->type == GFX_TOUCH_EVENT_PRESS) {
drag_off_x = (int32_t)event->x - ox;
drag_off_y = (int32_t)event->y - oy;
}
if (event->type == GFX_TOUCH_EVENT_MOVE) {
gfx_obj_set_pos(obj, (int32_t)event->x - drag_off_x, (int32_t)event->y - drag_off_y);
}
}
void make_draggable_label(gfx_disp_t *disp)
{
gfx_obj_t *label = gfx_label_create(disp);
gfx_label_set_text(label, "Drag me");
gfx_obj_set_pos(label, 50, 50);
gfx_obj_set_touch_cb(label, obj_touch_cb, NULL);
}
Text Scrolling
~~~~~~~~~~~~~~
Create a scrolling text label (see widget API for ``gfx_label_set_long_mode``, ``gfx_label_set_scroll_speed``, etc.):
.. code-block:: c
#include "gfx.h"
void setup_scroll_label(gfx_disp_t *disp)
{
gfx_obj_t *label = gfx_label_create(disp);
gfx_label_set_text(label, "This is a very long text that will scroll horizontally");
gfx_obj_set_size(label, 200, 30);
gfx_obj_set_pos(label, 10, 100);
gfx_label_set_long_mode(label, GFX_LABEL_LONG_SCROLL);
gfx_label_set_scroll_speed(label, 30);
gfx_label_set_scroll_loop(label, true);
}
Timer-Based Updates
~~~~~~~~~~~~~~~~~~~
Use the graphics timer to update widgets periodically. Timers are created on the **handle**:
.. code-block:: c
#include "gfx.h"
static gfx_obj_t *label = NULL;
static int counter = 0;
static void timer_callback(void *user_data)
{
gfx_handle_t handle = (gfx_handle_t)user_data;
gfx_emote_lock(handle);
if (label) {
gfx_label_set_text_fmt(label, "Counter: %d", counter++);
}
gfx_emote_unlock(handle);
}
void setup_timer_label(gfx_handle_t handle, gfx_disp_t *disp)
{
label = gfx_label_create(disp);
gfx_obj_set_pos(label, 50, 50);
gfx_timer_create(handle, timer_callback, 1000, handle);
}
QR Code Generation
~~~~~~~~~~~~~~~~~~
Generate and display a QR code:
.. code-block:: c
#include "gfx.h"
void setup_qrcode(gfx_disp_t *disp)
{
gfx_obj_t *qrcode = gfx_qrcode_create(disp);
gfx_qrcode_set_data(qrcode, "https://www.espressif.com");
gfx_qrcode_set_size(qrcode, 200);
gfx_qrcode_set_ecc(qrcode, GFX_QRCODE_ECC_MEDIUM);
gfx_obj_align(qrcode, GFX_ALIGN_CENTER, 0, 0);
}
Motion Scene Playback
~~~~~~~~~~~~~~~~~~~~~
Load a generated scene asset and start an action:
.. code-block:: c
#include "gfx.h"
#include "rig_active.inc"
static gfx_motion_player_t motion_player;
void setup_motion_scene(gfx_disp_t *disp)
{
gfx_motion_player_init(&motion_player, disp, &s_motion_scene_asset);
gfx_motion_player_set_canvas(&motion_player, 0, 0, 360, 360);
gfx_motion_player_set_action(&motion_player, 0, true);
}
For a complete interactive example with touch-driven movement and action switching, see ``test_apps/main/test_motion.c``.
Thread-Safe Operations
~~~~~~~~~~~~~~~~~~~~~~
When modifying widgets from another task, always use the graphics lock (on the **handle**):
.. code-block:: c
#include "gfx.h"
void update_widgets_from_task(gfx_handle_t handle)
{
gfx_emote_lock(handle);
gfx_label_set_text(label, "Updated from task");
gfx_obj_set_pos(img, new_x, new_y);
gfx_emote_unlock(handle);
}
Complete Application Example
-----------------------------
Initialization (core + one display), then create a label and refresh:
.. code-block:: c
#include "gfx.h"
#include "esp_log.h"
static const char *TAG = "gfx_app";
static gfx_handle_t gfx_handle = NULL;
static gfx_disp_t *gfx_disp = NULL;
static void disp_flush_callback(gfx_disp_t *disp, int x1, int y1, int x2, int y2, const void *data)
{
esp_lcd_panel_handle_t panel = (esp_lcd_panel_handle_t)gfx_disp_get_user_data(disp);
esp_lcd_panel_draw_bitmap(panel, x1, y1, x2, y2, data);
}
void app_main(void)
{
gfx_core_config_t gfx_cfg = {
.fps = 30,
.task = GFX_EMOTE_INIT_CONFIG(),
};
gfx_handle = gfx_emote_init(&gfx_cfg);
if (gfx_handle == NULL) {
ESP_LOGE(TAG, "Failed to initialize GFX");
return;
}
gfx_disp_config_t disp_cfg = {
.h_res = 320,
.v_res = 240,
.flush_cb = disp_flush_callback,
.update_cb = NULL,
.user_data = panel_handle, // your esp_lcd_panel_handle_t
.flags = { .swap = true },
.buffers = { .buf1 = NULL, .buf2 = NULL, .buf_pixels = 320 * 16 },
};
gfx_disp = gfx_disp_add(gfx_handle, &disp_cfg);
if (gfx_disp == NULL) {
ESP_LOGE(TAG, "Failed to add display");
gfx_emote_deinit(gfx_handle);
return;
}
gfx_obj_t *title = gfx_label_create(gfx_disp);
gfx_label_set_text(title, "ESP Emote GFX");
gfx_obj_align(title, GFX_ALIGN_TOP_MID, 0, 10);
gfx_label_set_color(title, GFX_COLOR_HEX(0x0000FF));
gfx_disp_refresh_all(gfx_disp);
ESP_LOGI(TAG, "GFX application started");
}
For more examples, see the test applications in the ``test_apps/`` directory.

View File

@ -0,0 +1,57 @@
ESP Emote GFX Documentation
===========================
Welcome to the ESP Emote GFX API documentation. This is a lightweight graphics framework for ESP-IDF with support for images, labels, animations, buttons, QR codes, fonts, and path-driven motion scenes.
.. toctree::
:maxdepth: 2
:caption: Contents:
overview
quickstart
motion_widget
api/core/index
api/widgets/index
examples
changelog
Overview
--------
ESP Emote GFX is a graphics framework designed for embedded systems, providing:
* **Images**: Display images in RGB565A8 format with alpha transparency
* **Animations**: GIF animations with ESP32 tools (EAF format)
* **Buttons**: Interactive button widgets with text, border, and pressed-state styling
* **Motion Scenes**: Path-based articulated widgets using joints, poses, actions, and mesh segments
* **Fonts**: LVGL fonts and FreeType TTF/OTF support
* **Timers**: Built-in timing system for smooth animations
* **Memory Optimized**: Designed for embedded systems with limited resources
Features
--------
* Lightweight and memory-efficient
* Thread-safe operations with mutex locking
* Support for multiple object types (images, labels, animations, buttons, QR codes, motion scenes)
* Flexible buffer management (internal or external buffers)
* Rich text rendering with scrolling and wrapping
* Animation playback control with segments and loops
* Path-driven character playback with touch-friendly scene runtime
Quick Links
-----------
* :doc:`Quick Start Guide <quickstart>`
* :doc:`Motion Widget Guide <motion_widget>`
* :doc:`Core API Reference <api/core/index>`
* :doc:`Widget API Reference <api/widgets/index>`
* :doc:`Examples <examples>`
* `Doxygen API Reference <../doxygen/index.html>`_ - Auto-generated C/C++ API documentation
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

View File

@ -0,0 +1,280 @@
# -*- coding: utf-8 -*-
"""Chinese (zh_CN) msgstr for Sphinx gettext catalogs. Keys must match .pot msgid exactly."""
# api: leave untranslated (empty dict) — HTML shows English for reference pages.
TRANSLATIONS_API: dict[str, str] = {}
TRANSLATIONS_INDEX: dict[str, str] = {
"Contents:": "目录:",
"ESP Emote GFX Documentation": "ESP Emote GFX 文档",
"Welcome to the ESP Emote GFX API documentation. This is a lightweight graphics framework for ESP-IDF with support for images, labels, animations, buttons, QR codes, and fonts.":
"欢迎使用 ESP Emote GFX API 文档。这是一个面向 ESP-IDF 的轻量级图形框架,支持图像、标签、动画、按钮、二维码与字体。",
"Overview": "概览",
"ESP Emote GFX is a graphics framework designed for embedded systems, providing:":
"ESP Emote GFX 面向嵌入式系统,提供以下能力:",
"**Images**: Display images in RGB565A8 format with alpha transparency":
"**图像**:以 RGB565A8 格式显示图像并支持 Alpha 透明",
"**Animations**: GIF animations with ESP32 tools (EAF format)":
"**动画**:配合 ESP32 工具使用 EAF 等格式",
"**Buttons**: Interactive button widgets with text, border, and pressed-state styling":
"**按钮**:可交互按钮组件,支持文本、边框与按下态样式",
"**Fonts**: LVGL fonts and FreeType TTF/OTF support":
"**字体**LVGL 字体与 FreeType TTF/OTF",
"**Timers**: Built-in timing system for smooth animations":
"**定时器**:内置时序系统,用于平滑动画",
"**Memory Optimized**: Designed for embedded systems with limited resources":
"**内存优化**:面向资源受限的嵌入式场景",
"Features": "特性",
"Lightweight and memory-efficient": "轻量且节省内存",
"Thread-safe operations with mutex locking": "通过互斥锁实现线程安全操作",
"Support for multiple object types (images, labels, animations, buttons, QR codes)":
"支持多种对象类型(图像、标签、动画、按钮、二维码)",
"Flexible buffer management (internal or external buffers)": "灵活的缓冲区管理(内部或外部)",
"Rich text rendering with scrolling and wrapping": "富文本渲染,支持滚动与换行",
"Animation playback control with segments and loops": "动画分段与循环播放控制",
"Quick Links": "快速链接",
":doc:`Quick Start Guide <quickstart>`": ":doc:`快速入门 <quickstart>`",
":doc:`Core API Reference <api/core/index>`": ":doc:`Core API 参考 <api/core/index>`",
":doc:`Widget API Reference <api/widgets/index>`": ":doc:`Widget API 参考 <api/widgets/index>`",
":doc:`Examples <examples>`": ":doc:`示例 <examples>`",
"`Doxygen API Reference <../doxygen/index.html>`_ - Auto-generated C/C++ API documentation":
"`Doxygen API 参考 <../doxygen/index.html>`_ — 自动生成的 C/C++ API 文档",
"Indices and tables": "索引与表格",
":ref:`genindex`": ":ref:`genindex`",
":ref:`modindex`": ":ref:`modindex`",
":ref:`search`": ":ref:`search`",
}
TRANSLATIONS_QUICKSTART: dict[str, str] = {
"Quick Start Guide": "快速入门",
"This guide will help you get started with ESP Emote GFX in just a few steps.":
"本指南帮助你在几个步骤内上手 ESP Emote GFX。",
"Installation": "安装",
"Add ESP Emote GFX to your ESP-IDF project by including it as a component. The component is available through the ESP Component Registry.":
"将 ESP Emote GFX 作为组件加入 ESP-IDF 工程;组件可在 ESP 组件注册表中获取。",
"Basic Setup": "基础设置",
"Include the main header:": "包含主头文件:",
"Initialize the graphics core (no display yet):": "初始化图形核心(尚未添加显示):",
"Add a display with a flush callback:": "添加显示设备并设置 flush 回调:",
"(Optional) Register panel IO callback so the framework knows when flush is done:":
"(可选)注册 panel IO 回调,以便框架获知 flush 完成:",
"(Optional) Add touch input:": "(可选)添加触摸输入:",
"Creating Your First Widget": "创建第一个组件",
"Widgets are created on a **display** (``gfx_disp_t *``), not on the handle.":
"组件创建在 display``gfx_disp_t *``)上,而不是在 handle 上。",
"Creating a Label": "创建标签",
"Creating an Image": "创建图像",
"Creating an Animation": "创建动画",
"Object touch callback (e.g. drag)": "对象触摸回调(例如拖拽)",
"Thread Safety": "线程安全",
"When modifying objects from outside the graphics task, use the graphics lock:":
"在图形任务之外修改对象时,请使用图形锁:",
"Complete Example": "完整示例",
"Next Steps": "下一步",
"Read the :doc:`Core API Reference <api/core/index>` for detailed API documentation":
"阅读 :doc:`Core API 参考 <api/core/index>` 获取完整 API 说明",
"Check out the :doc:`Widget API Reference <api/widgets/index>` for widget-specific functions":
"查看 :doc:`Widget API 参考 <api/widgets/index>` 了解各组件接口",
"See :doc:`Examples <examples>` for more complex usage patterns":
"参阅 :doc:`示例 <examples>` 获取更多用法",
}
TRANSLATIONS_EXAMPLES: dict[str, str] = {
"Examples": "示例",
"This section provides comprehensive examples demonstrating various features of ESP Emote GFX.":
"本节提供 ESP Emote GFX 各项能力的示例代码。",
"Initialization (core + display + optional touch)": "初始化(核心 + 显示 + 可选触摸)",
"Initialize the graphics core, add a display with flush callback, and optionally add touch. Widgets are created on the display (``gfx_disp_t *disp``).":
"初始化图形核心,添加带 flush 回调的显示设备,并可选择添加触摸。组件创建在显示(``gfx_disp_t *disp``)上。",
"Basic Examples": "基础示例",
"Simple Label": "简单标签",
"Create and display a simple text label on a display (``disp`` from ``gfx_disp_add``):":
"在显示上创建并显示简单文本标签(``disp`` 来自 ``gfx_disp_add``",
"Image Display": "图像显示",
"Display an image:": "显示图像:",
"Advanced Examples": "进阶示例",
"Multiple Widgets": "多组件",
"Create and manage multiple widgets on the same display:":
"在同一显示上创建并管理多个组件:",
"Touch and object callback (e.g. drag)": "触摸与对象回调(例如拖拽)",
"Register a per-object touch callback so the object receives PRESS/MOVE/RELEASE (e.g. for dragging):":
"注册逐对象的触摸回调,使对象接收 PRESS/MOVE/RELEASE用于拖拽等",
"Text Scrolling": "文本滚动",
"Create a scrolling text label (see widget API for ``gfx_label_set_long_mode``, ``gfx_label_set_scroll_speed``, etc.):":
"创建可滚动文本标签(详见 Widget API 中的 ``gfx_label_set_long_mode``、``gfx_label_set_scroll_speed`` 等):",
"Timer-Based Updates": "基于定时器的更新",
"Use the graphics timer to update widgets periodically. Timers are created on the **handle**:":
"使用图形定时器周期性更新组件。定时器创建在 **handle** 上:",
"QR Code Generation": "二维码生成",
"Generate and display a QR code:": "生成并显示二维码:",
"Thread-Safe Operations": "线程安全操作",
"When modifying widgets from another task, always use the graphics lock (on the **handle**):":
"从其他任务修改组件时,务必使用图形锁(在 **handle** 上):",
"Complete Application Example": "完整应用示例",
"Initialization (core + one display), then create a label and refresh:":
"初始化(核心 + 单个显示),再创建标签并刷新:",
"For more examples, see the test applications in the ``test_apps/`` directory.":
"更多示例见 ``test_apps/`` 目录中的测试应用。",
}
TRANSLATIONS_OVERVIEW: dict[str, str] = {
"Overview": "概览",
"ESP Emote GFX is a lightweight graphics framework for ESP-IDF that provides a simple yet powerful API for rendering graphics on embedded displays. It is designed with memory efficiency and performance in mind, making it ideal for resource-constrained embedded systems.":
"ESP Emote GFX 是面向 ESP-IDF 的轻量级图形框架,提供简洁而强大的嵌入式显示渲染 API在内存与性能之间取得平衡适合资源受限场景。",
"Architecture": "架构",
"The framework is built around a core object system where all graphical elements (images, labels, animations, buttons, QR codes) are treated as objects. These objects share common properties like position, size, visibility, and alignment.":
"框架以统一对象系统为核心:图像、标签、动画、按钮、二维码等元素均为对象,共享位置、尺寸、可见性与对齐等属性。",
"Core Components": "核心组件",
"Core System": "核心系统",
"The core system (`gfx_core`) manages:": "核心系统(`gfx_core`)负责:",
"Graphics context initialization and deinitialization": "图形上下文的初始化与反初始化",
"Buffer management (internal or external)": "缓冲区管理(内部或外部)",
"Rendering pipeline": "渲染管线",
"Thread safety with mutex locking": "基于互斥锁的线程安全",
"Screen refresh and invalidation": "屏幕刷新与脏区失效",
"Object System": "对象系统",
"The object system (`gfx_obj`) provides:": "对象系统(`gfx_obj`)提供:",
"Base object structure for all graphical elements": "所有图形元素的基类结构",
"Position and size management": "位置与尺寸管理",
"Alignment system (similar to LVGL)": "对齐系统(类似 LVGL",
"Visibility control": "可见性控制",
"Object lifecycle management": "对象生命周期管理",
"Timer System": "定时器系统",
"The timer system (`gfx_timer`) provides:": "定时器系统(`gfx_timer`)提供:",
"High-resolution timers for animations": "用于动画的高分辨率定时器",
"Callback-based timer events": "基于回调的定时事件",
"Repeat count and period control": "重复次数与周期控制",
"System tick management": "系统 tick 管理",
"Widgets": "组件",
"Image Widget": "图像组件",
"The image widget supports:": "图像组件支持:",
"RGB565 format (16-bit color)": "RGB56516 位色)",
"RGB565A8 format (16-bit color with 8-bit alpha)": "RGB565A816 位色 + 8 位 Alpha",
"C array and binary formats": "C 数组与二进制格式",
"Automatic format detection": "自动识别格式",
"Label Widget": "标签组件",
"The label widget provides:": "标签组件提供:",
"Text rendering with multiple font formats": "多种字体格式的文本渲染",
"LVGL font support": "LVGL 字体支持",
"FreeType TTF/OTF font support": "FreeType TTF/OTF 支持",
"Text alignment (left, center, right)": "文本对齐(左、中、右)",
"Long text handling (wrap, scroll, clip)": "长文本处理(换行、滚动、裁剪)",
"Background colors and opacity": "背景色与透明度",
"Button Widget": "按钮组件",
"The button widget provides:": "按钮组件提供:",
"Text label management": "文本标签管理",
"Normal and pressed background colors": "常态与按下背景色",
"Border color and width configuration": "边框颜色与宽度",
"Font and text alignment control": "字体与文本对齐控制",
"Animation Widget": "动画组件",
"The animation widget supports:": "动画组件支持:",
"EAF (ESP Animation Format) files": "EAFESP 动画格式)文件",
"Frame-by-frame playback control": "逐帧播放控制",
"Segment playback (start/end frames)": "分段播放(起止帧)",
"FPS control": "帧率控制",
"Loop and repeat options": "循环与重复选项",
"Mirror effects": "镜像效果",
"QR Code Widget": "二维码组件",
"The QR code widget provides:": "二维码组件提供:",
"Dynamic QR code generation": "动态生成二维码",
"Configurable size and error correction": "可配置尺寸与纠错等级",
"Custom foreground and background colors": "自定义前景与背景色",
"Memory Management": "内存管理",
"The framework supports two buffer management modes:": "框架支持两种缓冲区管理模式:",
"Internal Buffers": "内部缓冲区",
"The framework automatically allocates and manages frame buffers internally. This is the simplest mode but requires sufficient heap memory.":
"由框架在内部分配并管理帧缓冲。最简单,但需要足够的堆内存。",
"External Buffers": "外部缓冲区",
"You can provide your own buffers, allowing you to:": "你可自行提供缓冲区,从而可以:",
"Use memory-mapped regions": "使用内存映射区域",
"Control buffer placement (SRAM, SPIRAM, etc.)": "控制缓冲区位置SRAM、SPIRAM 等)",
"Optimize for specific memory constraints": "针对内存约束优化",
"Thread Safety": "线程安全",
"All widget operations should be performed within a graphics lock to ensure thread safety:":
"所有组件操作应在图形锁内执行以保证线程安全:",
"Dependencies": "依赖",
"ESP-IDF 5.0 or higher": "ESP-IDF 5.0 或更高版本",
"FreeType (for TTF/OTF font support)": "FreeTypeTTF/OTF 字体)",
"ESP New JPEG (for JPEG decoding)": "ESP New JPEGJPEG 解码)",
"License": "许可证",
"This project is licensed under the Apache License 2.0.": "本项目采用 Apache License 2.0 许可证。",
}
TRANSLATIONS_CHANGELOG: dict[str, str] = {
"Changelog": "更新日志",
"All notable changes to the ESP Emote GFX component will be documented in this file.":
"ESP Emote GFX 组件的重要变更将记录于此。",
"[3.0.3] - 2026-04-20": "[3.0.3] - 2026-04-20",
"Add `gfx_button` widget (text, font, normal/pressed colors, border)":
"新增 `gfx_button` 组件(文本、字体、常态/按下背景色、边框)",
"Add `gfx_log` API for log level configuration": "新增 `gfx_log` API用于配置日志级别",
"Documentation: separate English and Simplified Chinese HTML builds (gettext), language switcher, unified `postprocess_docs.sh` pipeline (API RST, Sphinx, Doxygen)":
"文档:英文与简体中文独立 HTML 构建gettext、页顶语言切换、统一 `postprocess_docs.sh` 流程API RST、Sphinx、Doxygen",
"Simplify GitHub Actions documentation job to a single build step":
"精简 GitHub Actions 文档构建为单一步骤",
"[3.0.2] - 2026-04-17": "[3.0.2] - 2026-04-17",
"Update version of esp_new_jpeg": "更新 esp_new_jpeg 版本",
"[3.0.1] - 2026-02-13": "[3.0.1] - 2026-02-13",
"Add CI build action for P4": "为 P4 添加 CI 构建",
"Optimize multi-buffer switching logic": "优化多缓冲切换逻辑",
"Fix crash when text is NULL": "修复 text 为 NULL 时的崩溃",
"Fix missing API documentation (e.g. gfx_touch_add)": "补全缺失的 API 文档(如 gfx_touch_add",
"[3.0.0] - 2026-01-22": "[3.0.0] - 2026-01-22",
"Add documentation build action": "添加文档构建 Action",
"Optimize EAF 8-bit render": "优化 EAF 8 位渲染",
"Fix FreeType parsing performance": "修复 FreeType 解析性能",
"Remove duplicated label-related APIs": "移除重复的标签相关 API",
"[2.1.0] - 2026-01-28": "[2.1.0] - 2026-01-28",
"Support for decoding Heatshrink-compressed image slices": "支持解码 Heatshrink 压缩的图像条带",
"[2.0.4] - 2026-01-22": "[2.0.4] - 2026-01-22",
"Fix Huffman+RLE decoding buffer sizing to prevent oversized output errors (Issue `#18 <https://github.com/espressif2022/esp_emote_gfx/issues/18>`_)":
"修复 Huffman+RLE 解码缓冲区尺寸避免输出过大错误Issue `#18 <https://github.com/espressif2022/esp_emote_gfx/issues/18>`_",
"[2.0.3] - 2026-01-08": "[2.0.3] - 2026-01-08",
"Delete local assets": "删除本地资源",
"Build acion for ['release-v5.2', 'release-v5.3', 'release-v5.4', 'release-v5.5']":
"为 ['release-v5.2', 'release-v5.3', 'release-v5.4', 'release-v5.5'] 构建 Action",
"Fix ESP-IDF version compatibility issues": "修复 ESP-IDF 版本兼容问题",
"Change flush_callback timeout from 20 ms to wait forever": "将 flush_callback 超时从 20 ms 改为无限等待",
"[2.0.2] - 2025-12-26": "[2.0.2] - 2025-12-26",
"Add optional JPEG decoding support for EAF animations": "为 EAF 动画增加可选 JPEG 解码",
"Center QR code rendering in UI layout": "在界面布局中居中渲染二维码",
"Add alpha channel support for animations": "为动画增加 Alpha 通道支持",
"[2.0.1] - 2025-12-05": "[2.0.1] - 2025-12-05",
"Add Touch event": "增加触摸事件",
"[2.0.0] - 2025-12-01": "[2.0.0] - 2025-12-01",
"Added partial refresh mode support": "增加局部刷新模式",
"Added QR code widget (gfx_qrcode)": "增加二维码组件 (gfx_qrcode)",
"[1.2.0] - 2025-09-0": "[1.2.0] - 2025-09-0",
"use eaf as a lib": "将 eaf 作为库使用",
"[1.1.2] - 2025-09-29": "[1.1.2] - 2025-09-29",
"Upgrade dependencies": "升级依赖",
"Update `espressif/esp_new_jpeg` to 0.6.x by @Kevincoooool. `#8 <https://github.com/espressif2022/esp_emote_gfx/pull/8>`_":
"将 `espressif/esp_new_jpeg` 升级到 0.6.x@Kevincoooool。`#8 <https://github.com/espressif2022/esp_emote_gfx/pull/8>`_",
"[1.1.1] - 2025-09-23": "[1.1.1] - 2025-09-23",
"Fixed": "修复",
"Resolve image block decoding failure in specific cases. `#6 <https://github.com/espressif2022/esp_emote_gfx/issues/6>`_":
"解决特定场景下图块解码失败。`#6 <https://github.com/espressif2022/esp_emote_gfx/issues/6>`_",
"[1.0.0] - 2025-08-01": "[1.0.0] - 2025-08-01",
"Added": "新增",
"Initial release of ESP Emote GFX framework": "ESP Emote GFX 框架首次发布",
"Core graphics rendering engine": "核心图形渲染引擎",
"Object system for images and labels": "图像与标签对象系统",
"Basic drawing functions and color utilities": "基础绘制与颜色工具",
"Software blending capabilities": "软件混合能力",
"Timer system for animations": "用于动画的定时器系统",
"Support for ESP-IDF 5.0+": "支持 ESP-IDF 5.0+",
"FreeType font rendering integration": "集成 FreeType 字体渲染",
"JPEG image decoding support": "JPEG 图像解码支持",
"Features": "特性",
"Lightweight graphics framework optimized for embedded systems": "面向嵌入式系统的轻量图形框架",
"Memory-efficient design for resource-constrained environments": "面向资源受限环境的省内存设计",
}
TRANSLATIONS_BY_CATALOG: dict[str, dict[str, str]] = {
"index": TRANSLATIONS_INDEX,
"overview": TRANSLATIONS_OVERVIEW,
"quickstart": TRANSLATIONS_QUICKSTART,
"examples": TRANSLATIONS_EXAMPLES,
"changelog": TRANSLATIONS_CHANGELOG,
"api": TRANSLATIONS_API,
}

View File

@ -0,0 +1,386 @@
Motion, Mesh Image, and Rendering Architecture
==============================================
Purpose
-------
This document explains how the motion scene stack is split today and where
future optimization work should happen. It focuses on these files:
* ``src/widget/motion/gfx_motion_scene.c``
* ``src/widget/motion/gfx_motion_player.c``
* ``src/widget/motion/gfx_motion_primitives.c``
* ``src/widget/motion/gfx_motion_style.c``
* ``src/widget/img/gfx_mesh_img.c``
* ``src/core/draw/gfx_blend.c``
The key design rule is that scene playback, segment-to-mesh conversion, mesh
image drawing, and low-level blending are separate layers. Each layer owns one
kind of state and should not reach across the boundary unless it is exposing a
small, reusable API.
High-Level Flow
---------------
The runtime path is:
.. code-block:: text
gfx_motion_asset_t
|
v
gfx_motion_scene.c
validate asset, own action timeline, update pose_cur/pose_tgt
|
v
gfx_motion_player.c
own mesh objects, callbacks, canvas mapping, segment dispatch
|
+--> gfx_motion_primitives.c
| generate capsule/ring/bezier mesh geometry
|
+--> gfx_motion_style.c
bind palette/resource/opacity/layer/UV style
|
v
gfx_mesh_img.c
own mesh state, source image, UV/rest points, bounds,
draw each mesh cell or scanline-filled polygon
|
v
gfx_blend.c
triangle rasterization, image sampling, polygon fill, AA, clipping
|
v
display buffer
Layer Ownership
---------------
``gfx_motion_scene.c`` is the parser and timeline layer.
It owns:
* Asset validation.
* Pose state: ``pose_cur`` and ``pose_tgt``.
* Active action, active step, step ticks, loop override.
* Interpolation policy such as ``HOLD`` and ``DAMPED``.
* Facing/mirroring when loading a target pose.
It must not own:
* Display objects.
* Mesh grids.
* Pixel colors or image descriptors.
* Rendering decisions such as scanline fill or triangle fallback.
``gfx_motion_player.c`` is the presentation adapter for motion scenes.
It owns:
* One ``gfx_mesh_img`` object per segment.
* Mapping from design-space scene coordinates into the destination canvas.
* Per-segment grid setup and cached grid size.
* Motion driver callbacks that connect ``gfx_motion_t`` to the scene and mesh
objects.
* Dispatching each segment to the primitive and style helpers.
It must not own:
* Asset timeline rules beyond calling ``gfx_motion_scene_*``.
* Generic mesh drawing.
* Low-level triangle rasterization or polygon fill.
* Primitive geometry algorithms or style/resource binding details.
``gfx_motion_primitives.c`` is the motion geometry algorithm layer.
It owns:
* Segment tessellation for capsule, ring, Bezier stroke, and Bezier fill.
* Primitive-local scratch usage through ``gfx_motion_player_runtime_scratch_t``.
* Cubic Bezier position/tangent evaluation.
* Stroke extrusion and fill mesh generation.
It must not own:
* Action playback.
* Display object lifetime.
* Palette/resource binding.
* Generic mesh drawing internals.
``gfx_motion_style.c`` is the motion style/resource binding layer.
It owns:
* Runtime solid color, palette color, opacity, texture source, UV crop, and
layer visibility helpers.
* Resource UV mapping into mesh ``rest_points``.
* Binding the correct image source for each segment.
It must not own:
* Primitive geometry.
* Action playback.
* Mesh cell rasterization.
``gfx_mesh_img.c`` is the generic deformable image widget.
It owns:
* The current mesh grid size and point count.
* ``points``: current object-local mesh geometry.
* ``rest_points``: reference UV/sample coordinates for the source image.
* Source image descriptor and decoded image header.
* Object bounds derived from current points.
* Mesh options such as column wrapping, inward AA, opacity, control-point debug
drawing, and scanline fill.
* Drawing by splitting each mesh cell into triangles, or by using scanline
polygon fill for solid filled shapes.
It must not own:
* Motion scene semantics.
* Segment kinds such as capsule or Bezier.
* Action playback or pose interpolation.
``gfx_blend.c`` is the software raster backend.
It owns:
* Image triangle drawing with source UVs.
* Polygon fill coverage.
* Clipping against buffer and object clip areas.
* Alpha blending and RGB565 byte swap handling.
* Anti-aliasing policy for primitive edges.
* Chunking wide polygon coverage so large filled shapes do not disappear.
It must not own:
* Widget state.
* Mesh object layout.
* Motion-specific assumptions.
Scene Asset Model
-----------------
``gfx_motion_asset_t`` is the ROM-side bundle consumed by the runtime. It is
defined in ``include/widget/gfx_motion_scene.h`` and contains:
* ``meta``: schema version and design-space viewbox.
* ``joint_names`` and ``joint_count``: named control points.
* ``segments``: visual primitives referencing joints.
* ``poses``: complete joint coordinate snapshots.
* ``actions``: step sequences that select target poses.
* ``sequence``: optional default playback sequence.
* ``layout``: default stroke, mirror axis, timing, and damping hints.
* ``resources``: optional texture images with UV crop.
* ``color_palette``: optional fixed segment colors.
The scene layer validates structural invariants early:
* Viewbox dimensions must be positive.
* Joint, pose, action, and sequence pointers must match their counts.
* Segment joint ranges must stay within ``joint_count``.
* Bezier control counts must satisfy ``3k + 1``.
* Resource and palette indices must resolve.
* Resource UV crop must fit inside the image descriptor.
* Layer bits must be within the 32-bit layer mask range.
Playback Model
--------------
``gfx_motion_scene_init()`` validates the asset and initializes the first action
step. The scene starts with ``pose_cur`` snapped to ``pose_tgt``.
``gfx_motion_scene_advance()`` advances the action timeline. It updates the
active step when ``hold_ticks`` expires, loads the new target pose, and applies
the step's interpolation policy.
``gfx_motion_scene_tick()`` eases ``pose_cur`` toward ``pose_tgt`` for damped
steps. The function returns whether coordinates changed. The player uses that
signal, plus dirty flags, to decide whether to update mesh objects.
``GFX_MOTION_INTERP_HOLD`` means snap immediately to the target pose. It is
used both on action switch and on step advance.
Player Segment Pipeline
-----------------------
``gfx_motion_player_init()`` creates one ``gfx_mesh_img`` object per segment.
The initial grid is chosen from the segment kind:
* Capsule: ``1 x 1`` grid, four points.
* Ring: ``N x 1`` wrapped grid, two point rows.
* Bezier strip: sampled curve columns, non-wrapped grid.
* Bezier loop: sampled curve columns, wrapped grid.
* Bezier fill: preset eye/ellipse grid or generic closed-loop rim grid.
On each motion apply callback, the player:
1. Checks whether the scene or mesh is dirty.
2. Converts needed joints from design space into screen space.
3. Computes stroke width and radius in screen pixels.
4. Applies the matching segment primitive into the mesh object.
5. Sets object visibility from the layer mask.
6. Clears dirty flags after all visible segments have been updated.
Primitive conversion details in ``gfx_motion_primitives.c``:
* Capsule computes a thick rectangle aligned with the segment direction.
* Ring computes outer and inner circular point rows and enables wrapped columns.
* Bezier stroke evaluates cubic position and analytic tangent, then extrudes
left/right normals into two mesh rows.
* Bezier fill either uses an eye/ellipse preset path or builds a hub/rim mesh
for generic closed loops.
Styling and Resources
---------------------
``gfx_motion_style.c`` binds image sources in this priority order:
1. ``resource_idx`` texture image.
2. ``color_idx`` palette 1x1 image.
3. Runtime solid 1x1 image.
For texture resources, ``uv_x``, ``uv_y``, ``uv_w``, and ``uv_h`` are mapped
into mesh ``rest_points``. The mesh's current ``points`` still describe screen
geometry; ``rest_points`` describe where to sample the source image. This keeps
UV crop generic and lets the same mesh renderer draw both full-image and
cropped-resource segments.
For palette and runtime solid colors, the source is a 1x1 RGB565 image. Filled
Bezier segments may additionally enable scanline fill so solid closed shapes do
not need to be rasterized through textured triangles.
Mesh Image Model
----------------
``gfx_mesh_img`` stores two point arrays:
* ``points`` are object-local Q8 geometry coordinates.
* ``rest_points`` are object-local Q8 source sampling coordinates.
For a plain image, both arrays start as a regular grid over the image. For
motion segments, the player continuously updates ``points`` while ``rest_points``
remain the source UV reference. Texture crop updates only ``rest_points``.
When ``points`` change, ``gfx_mesh_img_update_bounds()`` recalculates the object
bounding box. The draw origin is derived from the object position minus the
minimum mesh bound. This allows meshes with negative local coordinates while
still drawing through the normal object geometry system.
Important mesh options:
* ``wrap_cols`` connects the last column back to the first. Rings and closed
Bezier loops need this.
* ``aa_inward`` makes edge AA fade inward to avoid halos on thin strokes.
* ``scanline_fill`` bypasses textured triangle drawing for solid filled
polygons when possible.
* ``opacity`` applies uniform per-segment alpha.
Draw Pipeline
-------------
``gfx_mesh_img_draw()`` opens the image decoder, resolves RGB565 or RGB565A8
payloads, computes the clipped object area, and chooses one of two paths.
Scanline fill path:
* Used for selected solid filled polygons.
* Builds a polygon from mesh points.
* Calls ``gfx_sw_blend_polygon_fill()``.
* Falls back to triangle drawing if the scanline scratch capacity is too small.
Triangle path:
* Iterates every mesh cell.
* Builds four vertices with screen position and source UV.
* Splits the quad into two triangles.
* Chooses the shorter diagonal to reduce cracks on deformed quads.
* Marks internal edges so AA does not darken shared seams.
* Calls ``gfx_sw_blend_img_triangle_draw()`` twice per cell.
Low-Level Drawing
-----------------
``gfx_sw_blend_img_triangle_draw()`` samples the source image inside a triangle
and blends into the destination buffer. It handles:
* Screen clipping.
* Source UV interpolation.
* RGB565/RGB565A8 source alpha.
* Uniform opacity.
* Internal edge suppression.
* Optional inward AA.
``gfx_sw_blend_polygon_fill()`` fills a polygon with a solid color. It clips to
the destination buffer, computes per-pixel coverage, and chunks wide polygons
across X so coverage scratch memory stays bounded.
Module Boundary Assessment
--------------------------
The current module split is intentionally layered:
* ``scene.c`` is pure playback state.
* ``player.c`` is motion-scene runtime orchestration.
* ``primitives.c`` is motion geometry generation.
* ``style.c`` is motion style/resource binding.
* ``mesh_img.c`` is reusable deformable image infrastructure.
* ``gfx_blend.c`` is low-level rasterization.
The main area to watch from here is whether primitive APIs stabilize. If more
primitive families are added, keep them in ``gfx_motion_primitives.c`` until the
file itself becomes too large; only then split by primitive family.
Optimization Guide
------------------
Useful optimization entry points:
* Player dirty flags: avoid applying meshes when pose and canvas are unchanged.
* Cached segment grids: avoid reallocating mesh points in hot paths.
* Bezier sampling density: tune stroke and fill samples separately.
* Resource UV updates: avoid recomputing rest points unless grid or resource
crop changes.
* Mesh bounds: keep clamping warnings visible because excessive bounds can hide
real coordinate bugs.
* Scanline fill: prefer it for solid large fills; keep triangle fallback for
textured or unsupported cases.
* Blend chunk width: increase only if stack/static scratch budget allows it.
* Layer mask: hide inactive segment groups before tessellation if many layers
become common.
Testing Checklist
-----------------
When changing this stack, test these cases:
* Empty segment assets still initialize and deinitialize safely.
* ``HOLD`` actions snap immediately on init, action switch, and step advance.
* Palette segments are not overwritten by ``gfx_motion_player_set_color()``.
* Texture segments respect resource UV crop.
* Ring grid changes do not lose UV crop.
* Layer mask hides and restores segment visibility.
* Bezier strokes do not show dashed/bowtie artifacts on tight curves.
* Oversized scanline fills fall back to triangle rendering instead of blanking.
* Wide polygon fills render in chunks instead of returning early.
* Mesh allocation failure preserves the previous grid when possible.
* Bounds that exceed geometry range are clamped and logged.
Change Guidelines
-----------------
Use these rules when iterating:
* Put timeline or action behavior in ``gfx_motion_scene.c``.
* Put segment-to-mesh conversion in ``gfx_motion_primitives.c``.
* Put palette/resource/layer/opacity handling in ``gfx_motion_style.c``.
* Put generic mesh storage, UV, bounds, and draw dispatch in ``gfx_mesh_img.c``.
* Put pixel coverage, sampling, AA, and blend math in ``gfx_blend.c``.
* Keep public structs in ``include/widget/gfx_motion_scene.h`` stable where
possible because generated assets depend on them.
* Validate asset mistakes in the scene layer rather than letting the player or
renderer fail later.
* Keep renderer fallbacks visible through logs instead of silently drawing
nothing.

View File

@ -0,0 +1,342 @@
Motion、Mesh Image 与绘制架构说明
==================================
文档目标
--------
这份文档用于说明当前 motion scene、mesh image 和底层绘制链路的模块划分,方便后续做性能优化、功能迭代和代码 review。重点覆盖下面几个文件
* ``src/widget/motion/gfx_motion_scene.c``
* ``src/widget/motion/gfx_motion_player.c``
* ``src/widget/motion/gfx_motion_primitives.c``
* ``src/widget/motion/gfx_motion_style.c``
* ``src/widget/img/gfx_mesh_img.c``
* ``src/core/draw/gfx_blend.c``
当前架构的核心原则是动作播放、segment 到 mesh 的转换、mesh image 绘制、底层像素混合是四个独立层次。每一层只维护自己负责的状态;如果需要跨层交互,应通过小而稳定的 API 完成,而不是让上层直接依赖下层内部实现。
整体数据流
----------
运行时链路如下:
.. code-block:: text
gfx_motion_asset_t
|
v
gfx_motion_scene.c
校验 asset维护 action 时间线,更新 pose_cur/pose_tgt
|
v
gfx_motion_player.c
管理 mesh 对象、callback、canvas 映射和 segment dispatch
|
+--> gfx_motion_primitives.c
| 生成 capsule/ring/bezier 的 mesh 几何
|
+--> gfx_motion_style.c
绑定 palette/resource/opacity/layer/UV 样式
|
v
gfx_mesh_img.c
维护 mesh 状态、图片源、UV/rest_points、bounds
绘制每个 mesh cell 或 scanline 填充多边形
|
v
gfx_blend.c
三角形光栅化、图片采样、多边形填充、AA、裁剪
|
v
display buffer
模块职责边界
------------
``gfx_motion_scene.c`` 是 parser 和 action timeline 层。
它负责:
* 校验 ``gfx_motion_asset_t`` 的结构合法性。
* 维护 pose 状态:``pose_cur````pose_tgt``
* 维护当前 action、当前 step、step tick、loop override。
* 处理 ``HOLD````DAMPED`` 等插值策略。
* 加载 target pose 时处理 facing 和 mirror。
它不应该负责:
* display object 的创建或销毁。
* mesh grid 的尺寸或点位。
* 像素颜色、图片 descriptor、palette 图片。
* scanline fill、triangle fallback 等绘制策略。
``gfx_motion_player.c`` 是 motion scene 到显示对象的适配层。
它负责:
* 每个 segment 创建并持有一个 ``gfx_mesh_img`` 对象。
* 将设计空间坐标映射到目标 canvas 的屏幕坐标。
* 设置每个 segment 的 mesh grid并缓存 grid 尺寸以避免热路径重复分配。
* 提供 ``gfx_motion_t`` 的 tick/apply callback把 scene 状态同步到 mesh 对象。
* 将 segment 分发给 primitive helper 和 style helper。
它不应该负责:
* action timeline 规则本身,除了调用 ``gfx_motion_scene_*``
* 通用 mesh 绘制逻辑。
* 底层三角形光栅化、多边形填充或 alpha blend。
* primitive 几何算法和资源样式绑定细节。
``gfx_motion_primitives.c`` 是 motion 几何算法层。
它负责:
* 将 capsule、ring、Bezier stroke、Bezier fill 转换成 mesh 点。
* 通过 ``gfx_motion_player_runtime_scratch_t`` 使用 primitive 局部 scratch。
* cubic Bezier 位置和 tangent 计算。
* stroke extrusion 和 fill mesh generation。
它不应该负责:
* action 播放。
* display object 生命周期。
* palette/resource 绑定。
* 通用 mesh 绘制内部细节。
``gfx_motion_style.c`` 是 motion 样式和资源绑定层。
它负责:
* runtime solid color、palette color、opacity、texture source、UV crop、layer visibility helper。
* 将 resource UV 映射到 mesh ``rest_points``
* 为每个 segment 绑定正确的 image source。
它不应该负责:
* primitive 几何。
* action 播放。
* mesh cell 光栅化。
``gfx_mesh_img.c`` 是通用的可变形图片 widget。
它负责:
* 当前 mesh grid 尺寸和 point count。
* ``points``:当前 object-local 的 mesh 几何坐标。
* ``rest_points``:源图片采样坐标,也就是 UV/reference points。
* source image descriptor 和解码后的 image header。
* 根据当前 ``points`` 计算 object bounds。
* mesh 选项:``wrap_cols````aa_inward````opacity``、control point debug drawing、``scanline_fill``
* 将 mesh cell 拆成三角形绘制,或对 solid fill 使用 scanline polygon fill。
它不应该负责:
* motion scene 的语义。
* capsule、ring、Bezier 等 segment kind。
* action 播放或 pose 插值。
``gfx_blend.c`` 是软件绘制后端。
它负责:
* 带 UV 的图片三角形绘制。
* 多边形填充 coverage 计算。
* buffer area 和 clip area 裁剪。
* alpha blend 和 RGB565 byte swap。
* primitive 边缘的抗锯齿策略。
* 对超宽 polygon fill 按 X 方向分块,避免大形状因为 coverage buffer 上限直接不绘制。
它不应该负责:
* widget 状态。
* mesh object layout。
* motion 专用假设。
Scene Asset 模型
----------------
``gfx_motion_asset_t`` 是 ROM 侧的 scene bundle由运行时消费定义在 ``include/widget/gfx_motion_scene.h``。它包含:
* ``meta``schema version 和设计空间 viewbox。
* ``joint_names`` / ``joint_count``:命名控制点。
* ``segments``:引用 joints 的可视 primitive。
* ``poses``:完整的 joint 坐标快照。
* ``actions``:由多个 step 组成的动作序列,每个 step 指向一个 target pose。
* ``sequence``:可选的默认播放序列。
* ``layout``:默认 stroke、mirror axis、timer period、damping 等 hint。
* ``resources``:可选纹理图片及 UV crop。
* ``color_palette``:可选固定 segment 颜色。
scene 层会提前校验这些结构不变量:
* viewbox 宽高必须为正数。
* joint、pose、action、sequence 的 pointer 必须和 count 匹配。
* segment 引用的 joint 范围必须在 ``joint_count`` 内。
* Bezier 控制点数量必须满足 ``3k + 1``
* resource index 和 palette index 必须能解析到有效条目。
* resource UV crop 必须落在 image descriptor 范围内。
* layer bit 必须在 32-bit layer mask 可表达范围内。
播放模型
--------
``gfx_motion_scene_init()`` 负责校验 asset 并初始化第一个 action step。初始化后``pose_cur`` 会直接 snap 到 ``pose_tgt``
``gfx_motion_scene_advance()`` 负责推进 action timeline。当 ``hold_ticks`` 到期时,它切到下一个 step加载新的 target pose并应用该 step 的插值策略。
``gfx_motion_scene_tick()`` 负责将 ``pose_cur````pose_tgt`` 推进。对 ``DAMPED`` step它会做 easing函数返回坐标是否发生变化。player 会结合这个返回值和 dirty flag 判断是否需要更新 mesh 对象。
``GFX_MOTION_INTERP_HOLD`` 表示立即 snap 到 target pose。它在 init、action switch、step advance 时都应该生效。
Player Segment 管线
-------------------
``gfx_motion_player_init()`` 会为每个 segment 创建一个 ``gfx_mesh_img`` 对象。初始 grid 由 segment kind 决定:
* Capsule``1 x 1`` grid共 4 个点。
* Ring``N x 1`` wrapped grid两行点分别表示外圈和内圈。
* Bezier strip按曲线采样生成列不 wrap。
* Bezier loop按曲线采样生成列并启用 wrap。
* Bezier fill使用 eye/ellipse preset grid或 generic closed-loop rim grid。
每次 motion apply callback 中player 会:
1. 检查 scene 或 mesh 是否 dirty。
2. 将当前 segment 需要的 joints 从设计坐标转换到屏幕坐标。
3. 根据 canvas scale 计算 stroke width 和 radius。
4. 按 segment kind 调用对应 primitive apply helper更新 mesh points。
5. 根据 layer mask 设置 object visible。
6. 所有可见 segment 更新完成后清理 dirty flag。
``gfx_motion_primitives.c`` 中的 primitive 转换细节:
* Capsule 根据两个端点和 stroke width 生成一个沿线段方向的厚矩形。
* Ring 生成外圈和内圈两行圆形采样点,并启用 column wrap。
* Bezier stroke 计算 cubic 位置和解析 tangent再沿左右法线挤出成两行 mesh。
* Bezier fill 对 eye/ellipse 使用 preset path对 generic closed loop 构建 hub/rim mesh。
样式与资源
----------
``gfx_motion_style.c`` 按下面优先级绑定 image source
1. ``resource_idx``:纹理图片。
2. ``color_idx``palette 生成的 1x1 图片。
3. runtime solid 1x1 图片。
对 texture resource``uv_x````uv_y````uv_w````uv_h`` 会映射到 mesh 的 ``rest_points``。mesh 当前 ``points`` 仍然表示屏幕几何;``rest_points`` 表示源图片采样坐标。这样 UV crop 逻辑保持通用,同一个 mesh renderer 可以同时绘制整图纹理和裁剪后的 resource segment。
对 palette color 和 runtime solid colorsource 是一个 1x1 RGB565 image。Bezier fill segment 还可以启用 scanline fill让 solid closed shape 不必通过 textured triangles 光栅化。
Mesh Image 模型
---------------
``gfx_mesh_img`` 维护两组点:
* ``points``object-local Q8 几何坐标。
* ``rest_points``object-local Q8 源图片采样坐标。
普通图片中,这两组点初始都是覆盖整张图片的规则 grid。motion segment 中player 会持续更新 ``points`` 来改变屏幕形状,而 ``rest_points`` 保持为源 UV reference。texture crop 只更新 ``rest_points``
``points`` 改变时,``gfx_mesh_img_update_bounds()`` 会重新计算 object bounding box。draw origin 由 object position 减去 mesh 最小 bound 得出,因此 mesh 可以有负的 local coordinate同时仍然走正常 object geometry 系统绘制。
重要 mesh 选项:
* ``wrap_cols``把最后一列和第一列连起来。ring 和 closed Bezier loop 需要它。
* ``aa_inward``:让边缘 AA 向内衰减,避免细 stroke 外侧出现 halo。
* ``scanline_fill``:在条件满足时绕过 textured triangle drawing直接绘制 solid polygon。
* ``opacity``:应用 segment 级整体透明度。
绘制管线
--------
``gfx_mesh_img_draw()`` 会打开 image decoder解析 RGB565 或 RGB565A8 payload计算裁剪区域然后选择两条路径之一。
Scanline fill 路径:
* 用于部分 solid filled polygon。
* 从 mesh points 构造 polygon。
* 调用 ``gfx_sw_blend_polygon_fill()``
* 如果 scanline scratch capacity 不够,会 fallback 到 triangle drawing而不是直接空白。
Triangle 路径:
* 遍历每个 mesh cell。
* 用屏幕坐标和 source UV 构造四个 vertex。
* 将 quad 拆成两个 triangle。
* 选择较短对角线,减少变形 quad 上的裂缝。
* 标记内部边,避免 shared edge AA 产生深色缝。
* 每个 cell 调用两次 ``gfx_sw_blend_img_triangle_draw()``
底层绘制
--------
``gfx_sw_blend_img_triangle_draw()`` 在 triangle 内采样源图片并混合到目标 buffer。它处理
* 屏幕裁剪。
* source UV 插值。
* RGB565/RGB565A8 source alpha。
* uniform opacity。
* internal edge suppression。
* 可选 inward AA。
``gfx_sw_blend_polygon_fill()`` 用 solid color 填充 polygon。它负责裁剪、per-pixel coverage 计算,并对超宽 polygon 按 X 方向分块,保证 coverage scratch memory 有上限。
模块划分评估
------------
当前模块划分已经按层拆开:
* ``scene.c`` 是纯 playback state。
* ``player.c`` 是 motion scene runtime orchestration。
* ``primitives.c`` 是 motion geometry generation。
* ``style.c`` 是 motion style/resource binding。
* ``mesh_img.c`` 是可复用 deformable image 基础设施。
* ``gfx_blend.c`` 是底层 rasterization。
后续主要观察 ``gfx_motion_primitives.c`` 的体积。如果继续增加新的 primitive family先放在该文件中等 primitive API 和边界稳定后,再按 primitive family 进一步拆分。
优化入口
--------
后续优化可以优先看这些点:
* Player dirty flagspose 和 canvas 未变化时避免 apply mesh。
* Cached segment grids热路径避免重复 realloc mesh points。
* Bezier sampling densitystroke 和 fill 分别调采样密度。
* Resource UV updates只有 grid 或 resource crop 改变时才重算 rest points。
* Mesh bounds保留 clamp warning因为过大 bounds 往往暴露坐标 bug。
* Scanline fillsolid 大面积 fill 优先走 scanlinetextured 或 unsupported 情况保留 triangle fallback。
* Blend chunk width只有在 stack/static scratch 预算允许时才增大。
* Layer mask如果未来 layer 很多,可以在 tessellation 前跳过隐藏 segment group。
测试 Checklist
--------------
改动这条链路时建议覆盖这些 case
* 空 segment asset 可以安全 init/deinit。
* ``HOLD`` action 在 init、action switch、step advance 时都会立即 snap。
* palette segment 不会被 ``gfx_motion_player_set_color()`` 覆盖。
* texture segment 会尊重 resource UV crop。
* ring grid 动态变化后不会丢失 UV crop。
* layer mask 可以隐藏并恢复 segment。
* Bezier stroke 在急弯或短曲线下不出现 dashed/bowtie artifact。
* oversized scanline fill 会 fallback 到 triangle rendering而不是绘制空白。
* wide polygon fill 会按 chunk 绘制,而不是提前 return。
* mesh allocation failure 尽量保留旧 grid 状态。
* bounds 超过 geometry range 时会 clamp 并打 log。
迭代规则
--------
后续修改建议遵守这些规则:
* action timeline 或 pose 行为放在 ``gfx_motion_scene.c``
* segment-to-mesh conversion 放在 ``gfx_motion_primitives.c``
* palette/resource/layer/opacity 逻辑放在 ``gfx_motion_style.c``
* 通用 mesh storage、UV、bounds、draw dispatch 放在 ``gfx_mesh_img.c``
* pixel coverage、sampling、AA、blend math 放在 ``gfx_blend.c``
* ``include/widget/gfx_motion_scene.h`` 里的 public struct 尽量保持稳定,因为生成的 asset 依赖它们。
* asset 错误尽量在 scene 层校验,不要拖到 player 或 renderer 才失败。
* renderer fallback 要打 log避免静默绘制空白。

View File

@ -0,0 +1,103 @@
Motion Scene Widget
===================
The motion scene widget is the path-driven character and emote runtime in ESP Emote GFX. It is designed for assets exported as a ``gfx_motion_asset_t`` bundle and rendered through ``gfx_motion_player_t``.
When to Use It
--------------
Use the motion scene path when you need:
* Character or emote playback built from vector-like paths instead of bitmap frames
* Per-part styling with solid colors, palette colors, opacity, or texture binding
* Small action sets such as idle, move, happy, thinking, or touch-reactive actions
* Canvas-level movement where the whole character can swim, drift, or follow touch input
Core Model
----------
The motion scene asset has four main layers:
* ``joint_names`` and joint coordinates: named control points in design space
* ``segments``: visual primitives built from joints
* ``poses``: complete joint-coordinate snapshots
* ``actions``: sequences of pose steps with hold time, interpolation, and facing
The runtime owns a parser plus renderer:
* ``gfx_motion_scene_t`` manages pose interpolation and action state
* ``gfx_motion_player_t`` creates one ``gfx_mesh_img`` object per segment and applies the current pose to screen space
Segment Types
-------------
The current scene format supports these segment kinds:
* ``GFX_MOTION_SEG_CAPSULE``: thick limb/body stroke between two joints
* ``GFX_MOTION_SEG_RING``: circular outline around a center joint
* ``GFX_MOTION_SEG_BEZIER_STRIP``: open Bezier stroke
* ``GFX_MOTION_SEG_BEZIER_LOOP``: closed Bezier stroke
* ``GFX_MOTION_SEG_BEZIER_FILL``: closed filled Bezier region
Each segment can also carry:
* ``stroke_width`` override
* ``resource_idx`` for texture/image binding
* ``color_idx`` for palette-bound solid color
* ``opacity`` for per-part alpha
Playback Flow
-------------
Typical runtime usage:
1. Create or include a generated scene asset (for example from a designer/export pipeline).
2. Call ``gfx_motion_player_init()`` with a display and the asset.
3. Set the target canvas using ``gfx_motion_player_set_canvas()``.
4. Optionally set the runtime color with ``gfx_motion_player_set_color()``.
5. Select an initial action using ``gfx_motion_player_set_action()``.
6. When finished, call ``gfx_motion_player_deinit()``.
Example
-------
.. code-block:: c
#include "gfx.h"
#include "rig_active.inc"
static gfx_motion_player_t motion_player;
void motion_scene_start(gfx_disp_t *disp)
{
gfx_motion_player_init(&motion_player, disp, &s_motion_scene_asset);
gfx_motion_player_set_canvas(&motion_player, 0, 0, 360, 360);
gfx_motion_player_set_color(&motion_player, GFX_COLOR_HEX(0xFF7A00));
gfx_motion_player_set_action(&motion_player, 0, true);
}
void motion_scene_stop(void)
{
gfx_motion_player_deinit(&motion_player);
}
Interactive Example
-------------------
An end-to-end example is available in ``test_apps/main/test_motion.c``. It demonstrates:
* full-screen motion scene preview
* tap-to-switch action
* touch-guided movement by changing the runtime canvas
* timer-driven autonomous movement between touch interactions
Current Notes
-------------
The current implementation intentionally keeps the dependency chain small:
* no NanoVG dependency
* no libtess2 dependency
* filled polygon rendering uses the internal software path
This makes the widget easier to release and integrate into ESP-IDF projects, while keeping the scene model stable for designer/export tooling.

View File

@ -0,0 +1,158 @@
Overview
========
ESP Emote GFX is a lightweight graphics framework for ESP-IDF that provides a simple yet powerful API for rendering graphics on embedded displays. It is designed with memory efficiency and performance in mind, making it ideal for resource-constrained embedded systems.
Architecture
------------
The framework is built around a core object system where all graphical elements (images, labels, animations, buttons, QR codes, motion scenes) are treated as objects. These objects share common properties like position, size, visibility, and alignment.
Core Components
---------------
Core System
~~~~~~~~~~~
The core system (`gfx_core`) manages:
* Graphics context initialization and deinitialization
* Buffer management (internal or external)
* Rendering pipeline
* Thread safety with mutex locking
* Screen refresh and invalidation
Object System
~~~~~~~~~~~~~
The object system (`gfx_obj`) provides:
* Base object structure for all graphical elements
* Position and size management
* Alignment system (similar to LVGL)
* Visibility control
* Object lifecycle management
Timer System
~~~~~~~~~~~~
The timer system (`gfx_timer`) provides:
* High-resolution timers for animations
* Callback-based timer events
* Repeat count and period control
* System tick management
Widgets
-------
Image Widget
~~~~~~~~~~~~
The image widget supports:
* RGB565 format (16-bit color)
* RGB565A8 format (16-bit color with 8-bit alpha)
* C array and binary formats
* Automatic format detection
Label Widget
~~~~~~~~~~~~
The label widget provides:
* Text rendering with multiple font formats
* LVGL font support
* FreeType TTF/OTF font support
* Text alignment (left, center, right)
* Long text handling (wrap, scroll, clip)
* Background colors and opacity
Button Widget
~~~~~~~~~~~~~
The button widget provides:
* Text label management
* Normal and pressed background colors
* Border color and width configuration
* Font and text alignment control
Animation Widget
~~~~~~~~~~~~~~~~
The animation widget supports:
* EAF (ESP Animation Format) files
* Frame-by-frame playback control
* Segment playback (start/end frames)
* FPS control
* Loop and repeat options
* Mirror effects
QR Code Widget
~~~~~~~~~~~~~~
The QR code widget provides:
* Dynamic QR code generation
* Configurable size and error correction
* Custom foreground and background colors
Motion Scene Widget
~~~~~~~~~~~~~~~~~~~
The motion scene runtime provides:
* Path-driven articulated animation built from joints, poses, and actions
* Segment primitives for capsules, rings, open/closed Bezier strokes, and Bezier fills
* Per-segment solid color, palette color, opacity, or texture binding
* Display-space scaling through a configurable canvas and asset viewbox
* Touch-friendly runtime usage for interactive characters and emotes
The public entry points are ``gfx_motion_player_init()``, ``gfx_motion_player_set_canvas()``, ``gfx_motion_player_set_action()``, and ``gfx_motion_player_set_color()``. The underlying asset format is described by ``gfx_motion_asset_t`` in ``widget/gfx_motion_scene.h``.
Memory Management
-----------------
The framework supports two buffer management modes:
Internal Buffers
~~~~~~~~~~~~~~~~
The framework automatically allocates and manages frame buffers internally. This is the simplest mode but requires sufficient heap memory.
External Buffers
~~~~~~~~~~~~~~~~
You can provide your own buffers, allowing you to:
* Use memory-mapped regions
* Control buffer placement (SRAM, SPIRAM, etc.)
* Optimize for specific memory constraints
Thread Safety
-------------
All widget operations should be performed within a graphics lock to ensure thread safety:
.. code-block:: c
gfx_emote_lock(handle);
// Perform operations
gfx_obj_set_pos(obj, x, y);
gfx_label_set_text(label, "New text");
gfx_emote_unlock(handle);
Dependencies
------------
* ESP-IDF 5.0 or higher
* FreeType (for TTF/OTF font support)
* ESP New JPEG (for JPEG decoding)
* No NanoVG or libtess2 dependency is required for the current motion scene path
License
-------
This project is licensed under the Apache License 2.0.

View File

@ -0,0 +1,75 @@
#!/usr/bin/env bash
# ESP Emote GFX 文档本地预览脚本
# 一键构建并预览文档
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
PORT="${1:-8090}"
# Bind all interfaces by default so LAN URLs (e.g. http://10.x.x.x:PORT/) work; use 127.0.0.1 for local-only.
BIND_ADDR="${DOCS_PREVIEW_BIND:-0.0.0.0}"
cd "$REPO_ROOT"
echo "=========================================="
echo " ESP Emote GFX 文档本地预览"
echo "=========================================="
echo ""
# 关闭已存在的 http.server 进程
echo "[0/4] 检查并关闭已有服务..."
OLD_PIDS=$(ps -ef | grep "python.*http.server.*$PORT" | grep -v grep | awk '{print $2}')
if [ -n "$OLD_PIDS" ]; then
echo " 关闭端口 $PORT 上的旧进程: $OLD_PIDS"
echo "$OLD_PIDS" | xargs kill -9 2>/dev/null || true
sleep 1
else
echo " ✓ 无旧进程"
fi
# 检查并安装依赖
echo "[1/4] 检查依赖..."
if ! python3 -c "import sphinx" 2>/dev/null; then
echo " 安装 Sphinx 依赖..."
pip install -r docs/requirements.txt -q
else
echo " ✓ Sphinx 已安装"
fi
# 自动生成 API RST + 构建 Sphinx + 后处理 Doxygen
echo "[2/4] 自动生成并构建文档..."
if command -v doxygen >/dev/null 2>&1; then
bash docs/scripts/postprocess_docs.sh
echo " ✓ API 文档、Sphinx、Doxygen 全部完成"
else
bash docs/scripts/postprocess_docs.sh --skip-doxygen
echo " ✓ API 文档和 Sphinx 构建完成"
echo " ⚠ Doxygen 未安装,跳过 C/C++ API 文档"
echo " 安装方式: sudo apt-get install doxygen graphviz"
fi
# 启动本地服务器
echo "[3/4] 启动本地预览服务器..."
echo ""
echo "=========================================="
echo " 文档预览地址:"
echo ""
echo " http://127.0.0.1:$PORT (same host)"
echo " http://<this-machine-LAN-ip>:$PORT (other devices; server binds $BIND_ADDR)"
echo ""
echo " 主要页面EN / 中文 分目录;顶部可切换语言):"
echo " - 语言选择: http://127.0.0.1:$PORT/index.html"
echo " - English: http://127.0.0.1:$PORT/en/index.html"
echo " - 中文: http://127.0.0.1:$PORT/zh_CN/index.html"
echo " - Core API: http://127.0.0.1:$PORT/en/api/core/index.html"
echo " - Widget API: http://127.0.0.1:$PORT/en/api/widgets/index.html"
echo " - Doxygen: http://127.0.0.1:$PORT/doxygen/index.html"
echo ""
echo " 按 Ctrl+C 停止服务器"
echo "=========================================="
echo ""
cd docs/_build/html
python3 -m http.server "$PORT" --bind "$BIND_ADDR"

View File

@ -0,0 +1,236 @@
Quick Start Guide
=================
This guide will help you get started with ESP Emote GFX in just a few steps.
Installation
------------
Add ESP Emote GFX to your ESP-IDF project by including it as a component. The component is available through the ESP Component Registry.
Basic Setup
-----------
1. Include the main header:
.. code-block:: c
#include "gfx.h"
2. Initialize the graphics core (no display yet):
.. code-block:: c
gfx_core_config_t gfx_cfg = {
.fps = 30,
.task = GFX_EMOTE_INIT_CONFIG()
};
gfx_handle_t handle = gfx_emote_init(&gfx_cfg);
if (handle == NULL) {
ESP_LOGE(TAG, "Failed to initialize GFX");
return;
}
3. Add a display with a flush callback:
.. code-block:: c
void disp_flush_callback(gfx_disp_t *disp, int x1, int y1, int x2, int y2, const void *data)
{
void *panel = gfx_disp_get_user_data(disp);
// Send RGB565 data (x1,y1)-(x2,y2) to your panel, e.g. esp_lcd_panel_draw_bitmap(panel, x1, y1, x2, y2, data);
}
gfx_disp_config_t disp_cfg = {
.h_res = 320,
.v_res = 240,
.flush_cb = disp_flush_callback,
.update_cb = NULL,
.user_data = your_panel_handle, // e.g. esp_lcd_panel_handle_t
.flags = { .swap = true },
.buffers = { .buf1 = NULL, .buf2 = NULL, .buf_pixels = 320 * 16 },
};
gfx_disp_t *disp = gfx_disp_add(handle, &disp_cfg);
if (disp == NULL) {
ESP_LOGE(TAG, "Failed to add display");
gfx_emote_deinit(handle);
return;
}
4. (Optional) Register panel IO callback so the framework knows when flush is done:
.. code-block:: c
static bool flush_io_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
gfx_disp_t *disp = (gfx_disp_t *)user_ctx;
if (disp) {
gfx_disp_flush_ready(disp, true);
}
return true;
}
const esp_lcd_panel_io_callbacks_t cbs = { .on_color_trans_done = flush_io_ready };
esp_lcd_panel_io_register_event_callbacks(io_handle, &cbs, disp);
5. (Optional) Add touch input:
.. code-block:: c
void touch_event_cb(gfx_touch_t *touch, const gfx_touch_event_t *event, void *user_data)
{
// Handle PRESS / MOVE / RELEASE; event->x, event->y, event->hit_obj
}
gfx_touch_config_t touch_cfg = {
.handle = esp_lcd_touch_handle, // from your BSP or esp_lcd_touch_new
.event_cb = touch_event_cb,
.disp = disp,
.poll_ms = 50,
.user_data = NULL,
};
gfx_touch_t *touch = gfx_touch_add(handle, &touch_cfg);
Creating Your First Widget
--------------------------
Widgets are created on a **display** (``gfx_disp_t *``), not on the handle.
Creating a Label
~~~~~~~~~~~~~~~~
.. code-block:: c
gfx_obj_t *label = gfx_label_create(disp);
gfx_label_set_text(label, "Hello, World!");
gfx_obj_set_pos(label, 50, 50);
gfx_label_set_color(label, GFX_COLOR_HEX(0xFF0000)); // Red
Creating an Image
~~~~~~~~~~~~~~~~~
.. code-block:: c
gfx_obj_t *img = gfx_img_create(disp);
extern const gfx_image_dsc_t my_image;
gfx_img_set_src(img, (void *)&my_image);
gfx_obj_set_pos(img, 100, 100);
Creating an Animation
~~~~~~~~~~~~~~~~~~~~~
.. code-block:: c
gfx_obj_t *anim = gfx_anim_create(disp);
gfx_anim_set_src(anim, anim_data, anim_size);
gfx_obj_align(anim, GFX_ALIGN_CENTER, 0, 0);
gfx_anim_set_segment(anim, 0, 0xFFFF, 15, true);
gfx_anim_start(anim);
Creating a Rig Scene
~~~~~~~~~~~~~~~~~~~~
Rig scenes are created from a generated ``gfx_motion_asset_t`` and managed by ``gfx_motion_player_t``.
.. code-block:: c
#include "gfx.h"
#include "rig_active.inc"
static gfx_motion_player_t motion_player;
void setup_motion_scene(gfx_disp_t *disp)
{
gfx_motion_player_init(&motion_player, disp, &s_motion_scene_asset);
gfx_motion_player_set_canvas(&motion_player, 0, 0, 320, 240);
gfx_motion_player_set_color(&motion_player, GFX_COLOR_HEX(0xFF7A00));
gfx_motion_player_set_action(&motion_player, 0, true);
}
Use ``gfx_motion_player_deinit()`` when the scene is no longer needed.
Object touch callback (e.g. drag)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: c
void my_touch_cb(gfx_obj_t *obj, const gfx_touch_event_t *event, void *user_data)
{
if (event->type == GFX_TOUCH_EVENT_PRESS) { /* ... */ }
if (event->type == GFX_TOUCH_EVENT_MOVE) { gfx_obj_set_pos(obj, event->x, event->y); }
}
gfx_obj_set_touch_cb(label, my_touch_cb, NULL);
Thread Safety
-------------
When modifying objects from outside the graphics task, use the graphics lock:
.. code-block:: c
gfx_emote_lock(handle);
gfx_label_set_text(label, "Updated text");
gfx_obj_set_pos(img, new_x, new_y);
gfx_emote_unlock(handle);
Complete Example
----------------
.. code-block:: c
#include "gfx.h"
#include "esp_log.h"
static const char *TAG = "gfx_example";
static gfx_handle_t gfx_handle = NULL;
static gfx_disp_t *gfx_disp = NULL;
static void disp_flush_callback(gfx_disp_t *disp, int x1, int y1, int x2, int y2, const void *data)
{
esp_lcd_panel_handle_t panel = (esp_lcd_panel_handle_t)gfx_disp_get_user_data(disp);
esp_lcd_panel_draw_bitmap(panel, x1, y1, x2, y2, data);
}
void app_main(void)
{
gfx_core_config_t gfx_cfg = {
.fps = 30,
.task = GFX_EMOTE_INIT_CONFIG(),
};
gfx_handle = gfx_emote_init(&gfx_cfg);
if (gfx_handle == NULL) {
ESP_LOGE(TAG, "Failed to initialize GFX");
return;
}
gfx_disp_config_t disp_cfg = {
.h_res = 320,
.v_res = 240,
.flush_cb = disp_flush_callback,
.update_cb = NULL,
.user_data = panel_handle, // your esp_lcd_panel_handle_t
.flags = { .swap = true },
.buffers = { .buf1 = NULL, .buf2 = NULL, .buf_pixels = 320 * 16 },
};
gfx_disp = gfx_disp_add(gfx_handle, &disp_cfg);
if (gfx_disp == NULL) {
ESP_LOGE(TAG, "Failed to add display");
gfx_emote_deinit(gfx_handle);
return;
}
gfx_obj_t *label = gfx_label_create(gfx_disp);
gfx_label_set_text(label, "Hello, ESP Emote GFX!");
gfx_obj_set_pos(label, 50, 50);
gfx_label_set_color(label, GFX_COLOR_HEX(0x00FF00));
gfx_disp_refresh_all(gfx_disp);
ESP_LOGI(TAG, "GFX application started");
}
Next Steps
----------
* Read the :doc:`Core API Reference <api/core/index>` for detailed API documentation
* Read the :doc:`Rig Widget Guide <motion_widget>` for the scene asset model and playback flow
* Check out the :doc:`Widget API Reference <api/widgets/index>` for widget-specific functions
* See :doc:`Examples <examples>` for more complex usage patterns

View File

@ -0,0 +1,4 @@
esp-docs>=2.1.5
breathe>=4.35.0
Babel>=2.12.0

View File

@ -0,0 +1,645 @@
#!/usr/bin/env python3
"""
自动从 C 头文件生成 RST API 文档
使用方法:
python docs/scripts/generate_api_docs.py
功能:
1. 解析头文件中的 Doxygen 注释
2. 生成对应的 RST 文档
3. 支持结构体枚举函数宏等
"""
import re
import argparse
from pathlib import Path
from dataclasses import dataclass, field
from typing import List, Optional, Dict, Tuple
@dataclass
class DocItem:
"""文档项"""
name: str
kind: str # function, struct, enum, typedef, macro
brief: str = ""
description: str = ""
params: List[Dict] = field(default_factory=list)
returns: str = ""
code: str = ""
notes: List[str] = field(default_factory=list)
examples: List[str] = field(default_factory=list)
class HeaderParser:
"""解析 C 头文件"""
def __init__(self, filepath: str):
self.filepath = filepath
with open(filepath, 'r', encoding='utf-8') as f:
self.content = f.read()
self.items: List[DocItem] = []
def parse(self) -> List[DocItem]:
"""解析头文件"""
self._parse_typedefs()
self._parse_enums()
self._parse_structs()
self._parse_macros()
self._parse_functions()
return self.items
def _find_comment_before(self, pos: int) -> Optional[str]:
"""查找位置前最近的 Doxygen 注释块"""
before = self.content[:pos]
# 向前查找,跳过空白
search_pos = len(before) - 1
while search_pos >= 0 and before[search_pos] in ' \t\n':
search_pos -= 1
if search_pos < 0:
return None
# 检查是否以 */ 结尾(块注释)
if search_pos >= 1 and before[search_pos-1:search_pos+1] == '*/':
# 向前找 /**
start = before.rfind('/**', 0, search_pos)
if start != -1:
return before[start:search_pos+1]
return None
def _parse_doxygen_block(self, comment: str) -> Dict:
"""解析单个 Doxygen 注释块"""
result = {
'brief': '',
'params': [],
'returns': '',
'notes': [],
'examples': [],
}
if not comment:
return result
# 清理注释标记
lines = []
for line in comment.split('\n'):
# 移除 /**, */, *, //
line = re.sub(r'^\s*/?\*+/?', '', line)
line = re.sub(r'\*/$', '', line)
lines.append(line.strip())
text = '\n'.join(lines).strip()
# 提取 @brief
match = re.search(r'@brief\s+(.+?)(?=\n\s*@|\n\s*\n|$)', text, re.DOTALL)
if match:
result['brief'] = ' '.join(match.group(1).split())
# 提取 @param
for match in re.finditer(r'@param(?:\[(\w+)\])?\s+(\w+)\s+(.+?)(?=\n\s*@|\n\s*\n|$)', text, re.DOTALL):
direction = match.group(1) or 'in'
name = match.group(2)
desc = ' '.join(match.group(3).split())
result['params'].append({'name': name, 'direction': direction, 'desc': desc})
# 提取 @return
match = re.search(r'@returns?\s+(.+?)(?=\n\s*@|\n\s*\n|$)', text, re.DOTALL)
if match:
result['returns'] = ' '.join(match.group(1).split())
# 提取 @note
for match in re.finditer(r'@note\s+(.+?)(?=\n\s*@|\n\s*\n|$)', text, re.DOTALL):
result['notes'].append(' '.join(match.group(1).split()))
# 提取 @code...@endcode
for match in re.finditer(r'@code\s*(.+?)@endcode', text, re.DOTALL):
code = match.group(1).strip()
# 保持代码缩进
result['examples'].append(code)
return result
def _parse_typedefs(self):
"""解析简单 typedef"""
# typedef void *gfx_handle_t;
pattern = r'typedef\s+(\w+)\s*\*?\s*(\w+_t)\s*;'
for match in re.finditer(pattern, self.content):
name = match.group(2)
comment = self._find_comment_before(match.start())
parsed = self._parse_doxygen_block(comment)
self.items.append(DocItem(
name=name,
kind='typedef',
brief=parsed['brief'],
code=match.group(0).strip(),
))
# 函数指针 typedef
pattern = r'typedef\s+(\w+)\s*\(\s*\*\s*(\w+_t)\s*\)\s*\(([^)]*)\)\s*;'
for match in re.finditer(pattern, self.content):
name = match.group(2)
comment = self._find_comment_before(match.start())
parsed = self._parse_doxygen_block(comment)
self.items.append(DocItem(
name=name,
kind='typedef',
brief=parsed['brief'],
code=match.group(0).strip(),
))
def _parse_enums(self):
"""解析枚举"""
pattern = r'typedef\s+enum\s*\{([^}]+)\}\s*(\w+)\s*;'
for match in re.finditer(pattern, self.content):
name = match.group(2)
comment = self._find_comment_before(match.start())
parsed = self._parse_doxygen_block(comment)
self.items.append(DocItem(
name=name,
kind='enum',
brief=parsed['brief'],
code=match.group(0).strip(),
))
def _parse_structs(self):
"""解析结构体"""
pattern = r'typedef\s+struct\s*\{([\s\S]+?)\}\s*(\w+_t)\s*;'
for match in re.finditer(pattern, self.content):
name = match.group(2)
comment = self._find_comment_before(match.start())
parsed = self._parse_doxygen_block(comment)
self.items.append(DocItem(
name=name,
kind='struct',
brief=parsed['brief'],
code=match.group(0).strip(),
))
def _parse_macros(self):
"""解析宏定义"""
pattern = r'#define\s+(\w+)\s*\([^)]*\)[^\n]*(?:\\\n[^\n]*)*'
for match in re.finditer(pattern, self.content):
name = match.group(1)
if name.startswith('_'):
continue
comment = self._find_comment_before(match.start())
parsed = self._parse_doxygen_block(comment)
self.items.append(DocItem(
name=name,
kind='macro',
brief=parsed['brief'],
code=match.group(0).strip(),
))
def _parse_functions(self):
"""解析函数声明"""
# 匹配函数声明:返回类型 函数名(参数); 返回类型与函数名之间允许无空格(如 gfx_touch_t *gfx_touch_add
pattern = r'/\*\*[\s\S]*?\*/\s*\n\s*(\w+(?:\s*\*)?)\s*(\w+)\s*\(([^)]*)\)\s*;'
for match in re.finditer(pattern, self.content):
full_match = match.group(0)
ret_type = match.group(1).strip()
name = match.group(2)
# 提取注释部分
comment_end = full_match.find('*/')
if comment_end != -1:
comment = full_match[:comment_end+2]
else:
comment = None
parsed = self._parse_doxygen_block(comment)
# 构建函数签名
func_sig = f"{ret_type} {name}({match.group(3).strip()});"
self.items.append(DocItem(
name=name,
kind='function',
brief=parsed['brief'],
params=parsed['params'],
returns=parsed['returns'],
code=func_sig,
notes=parsed['notes'],
examples=parsed['examples'],
))
class RstGenerator:
"""生成 RST 文档"""
def __init__(self, module_name: str, title: str):
self.module_name = module_name
self.title = title
self.items: List[DocItem] = []
def add_items(self, items: List[DocItem]):
self.items.extend(items)
@staticmethod
def _underline(text: str, char: str) -> str:
return char * max(len(text), 3)
def generate(self) -> str:
"""生成 RST 内容"""
lines = []
# 标题
lines.append(self.title)
lines.append(self._underline(self.title, '='))
lines.append('')
# 按类型分组
macros = [i for i in self.items if i.kind == 'macro']
typedefs = [i for i in self.items if i.kind == 'typedef']
enums = [i for i in self.items if i.kind == 'enum']
structs = [i for i in self.items if i.kind == 'struct']
functions = [i for i in self.items if i.kind == 'function']
# 类型定义
if typedefs or enums or structs:
lines.append('Types')
lines.append(self._underline('Types', '-'))
lines.append('')
for item in typedefs:
lines.extend(self._format_type(item))
for item in enums:
lines.extend(self._format_type(item))
for item in structs:
lines.extend(self._format_type(item))
# 宏
if macros:
lines.append('Macros')
lines.append(self._underline('Macros', '-'))
lines.append('')
for item in macros:
lines.extend(self._format_macro(item))
# 函数
if functions:
lines.append('Functions')
lines.append(self._underline('Functions', '-'))
lines.append('')
for item in functions:
lines.extend(self._format_function(item))
return '\n'.join(lines)
def _format_type(self, item: DocItem) -> List[str]:
lines = []
lines.append(f'{item.name}')
lines.append('~' * len(item.name))
lines.append('')
if item.brief:
lines.append(item.brief)
lines.append('')
lines.append('.. code-block:: c')
lines.append('')
for code_line in item.code.split('\n'):
lines.append(f' {code_line}')
lines.append('')
return lines
def _format_macro(self, item: DocItem) -> List[str]:
lines = []
lines.append(f'{item.name}()')
lines.append('~' * (len(item.name) + 2))
lines.append('')
if item.brief:
lines.append(item.brief)
lines.append('')
lines.append('.. code-block:: c')
lines.append('')
for code_line in item.code.split('\n'):
lines.append(f' {code_line}')
lines.append('')
return lines
def _format_function(self, item: DocItem) -> List[str]:
lines = []
lines.append(f'{item.name}()')
lines.append('~' * (len(item.name) + 2))
lines.append('')
if item.brief:
lines.append(item.brief)
lines.append('')
lines.append('.. code-block:: c')
lines.append('')
lines.append(f' {item.code}')
lines.append('')
if item.params:
lines.append('**Parameters:**')
lines.append('')
for param in item.params:
lines.append(f"* ``{param['name']}`` - {param['desc']}")
lines.append('')
if item.returns:
lines.append('**Returns:**')
lines.append('')
lines.append(f'* {item.returns}')
lines.append('')
for note in item.notes:
lines.append('**Note:**')
lines.append('')
lines.append(note)
lines.append('')
for example in item.examples:
lines.append('**Example:**')
lines.append('')
lines.append('.. code-block:: c')
lines.append('')
for code_line in example.split('\n'):
lines.append(f' {code_line}')
lines.append('')
return lines
TITLE_OVERRIDES = {
'gfx_core': 'Core System',
'gfx_types': 'Types',
'gfx_disp': 'Display',
'gfx_touch': 'Touch',
'gfx_obj': 'Object',
'gfx_timer': 'Timer',
'gfx_img': 'Image',
'gfx_label': 'Label',
'gfx_anim': 'Animation',
'gfx_qrcode': 'QR Code',
'gfx_button': 'Button',
'gfx_font_lvgl': 'LVGL Font Compatibility',
}
# 各 API 子目录的 index 配置:(子目录名, 页面标题, 引言段落, “模块列表”小节标题)
INDEX_CONFIG = [
(
'core',
'Core API Reference',
'The core API provides the foundation for the graphics framework, including initialization, object management, and basic types.',
'Core Modules',
),
(
'widgets',
'Widget API Reference',
'The widget API provides specialized functionality for different types of graphical elements.',
'Widget Modules',
),
]
def stem_to_display_name(stem: str) -> str:
"""Convert `gfx_button` to `Button` as a fallback title."""
name = stem
if name.startswith('gfx_'):
name = name[4:]
return name.replace('_', ' ').title()
def title_for_header(stem: str) -> str:
if stem in TITLE_OVERRIDES:
return f"{TITLE_OVERRIDES[stem]} ({stem})"
fallback = stem_to_display_name(stem)
return f"{fallback} ({stem})"
def discover_header_mapping(repo_root: Path) -> List[Tuple[str, str, str]]:
mapping: List[Tuple[str, str, str]] = []
search_roots = [
('include/core', 'api/core'),
('include/widget', 'api/widgets'),
]
for header_dir, rst_dir in search_roots:
full_dir = repo_root / header_dir
if not full_dir.is_dir():
continue
for header_path in sorted(full_dir.glob('*.h')):
stem = header_path.stem
rel_header = str(Path(header_dir) / header_path.name)
rel_rst = str(Path(rst_dir) / f'{stem}.rst')
mapping.append((rel_header, rel_rst, title_for_header(stem)))
return mapping
def md_to_rst_line(line: str) -> str:
"""Convert a single Markdown line to RST (headers and list items)."""
s = line.rstrip()
# [text](url) -> `text <url>`_
s = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', r'`\1 <\2>`_', s)
if s.startswith('# '):
title = s[2:].strip()
return title + '\n' + '=' * len(title)
if s.startswith('## '):
title = s[3:].strip()
return title + '\n' + '-' * len(title)
if s.startswith('### '):
title = s[4:].strip()
return title + '\n' + '~' * len(title)
if s.startswith('- ') and not s.startswith('- [ ]'):
return '* ' + s[2:]
return s
def generate_changelog_rst(repo_root: Path, docs_dir: Path) -> bool:
"""Read CHANGELOG.md and write docs/changelog.rst (MD to RST conversion)."""
md_path = repo_root / 'CHANGELOG.md'
rst_path = docs_dir / 'changelog.rst'
if not md_path.is_file():
return False
with open(md_path, 'r', encoding='utf-8') as f:
md_lines = f.readlines()
rst_lines = []
for line in md_lines:
if not line.strip():
rst_lines.append('')
continue
rst_lines.append(md_to_rst_line(line))
rst_path.parent.mkdir(parents=True, exist_ok=True)
with open(rst_path, 'w', encoding='utf-8') as f:
f.write('\n'.join(rst_lines))
f.write('\n')
return True
def update_index_rst(api_dir: Path, subdir: str, title: str, intro: str, modules_heading: str) -> bool:
"""
扫描 api/<subdir>/ 下的 .rst 文件排除 index.rst生成 toctree 和模块列表写入 index.rst
每个条目的描述取自对应 .rst 文件的第一行标题行
返回是否写入了文件
"""
def underline(text: str, char: str) -> str:
return char * max(len(text), 3)
index_dir = api_dir / subdir
if not index_dir.is_dir():
return False
rst_files = sorted(
f.stem for f in index_dir.glob('*.rst') if f.name != 'index.rst'
)
if not rst_files:
return False
# 从每个 .rst 读取第一行作为描述
descriptions = {}
for stem in rst_files:
rst_path = index_dir / f'{stem}.rst'
try:
with open(rst_path, 'r', encoding='utf-8') as f:
first = f.readline()
descriptions[stem] = first.strip() if first else stem
except OSError:
descriptions[stem] = stem
lines = [
title,
underline(title, '='),
'',
intro,
'',
'.. toctree::',
' :maxdepth: 2',
'',
]
for stem in rst_files:
lines.append(f' {stem}')
lines.extend(['', modules_heading, underline(modules_heading, '-'), ''])
for stem in rst_files:
desc = descriptions[stem]
lines.append(f'* :doc:`{stem}` - {desc}')
lines.append('')
index_path = index_dir / 'index.rst'
with open(index_path, 'w', encoding='utf-8') as f:
f.write('\n'.join(lines))
return True
def run(args) -> int:
# 确定项目根目录
script_dir = Path(__file__).parent
repo_root = script_dir.parent.parent
if not args.quiet:
print("=" * 50)
print(" ESP Emote GFX API 文档生成器")
print("=" * 50)
print()
header_mapping = discover_header_mapping(repo_root)
generated = 0
for header_path, rst_path, title in header_mapping:
if args.header and args.header not in header_path:
continue
full_header_path = repo_root / header_path
full_rst_path = repo_root / args.output_dir / rst_path
if not full_header_path.exists():
if not args.quiet:
print(f"⚠ 跳过: {header_path} (文件不存在)")
continue
if not args.quiet:
print(f"处理: {header_path}")
print(f"{rst_path}")
# 解析头文件
parser_obj = HeaderParser(str(full_header_path))
items = parser_obj.parse()
# 统计各类型数量
counts = {}
for item in items:
counts[item.kind] = counts.get(item.kind, 0) + 1
count_str = ', '.join(f"{v} {k}" for k, v in counts.items())
if not args.quiet:
print(f" 发现: {count_str}")
# 生成 RST
generator = RstGenerator(header_path, title)
generator.add_items(items)
rst_content = generator.generate()
if args.dry_run:
if not args.quiet:
print(f" [dry-run] 将生成 {len(rst_content)} 字节")
else:
full_rst_path.parent.mkdir(parents=True, exist_ok=True)
with open(full_rst_path, 'w', encoding='utf-8') as f:
f.write(rst_content)
if not args.quiet:
print(f" ✓ 已生成")
generated += 1
if not args.quiet:
print()
# 根据 api/core 和 api/widgets 下的 .rst 自动更新 index.rst
api_dir = repo_root / args.output_dir / 'api'
if not args.dry_run and api_dir.is_dir():
for subdir, title, intro, modules_heading in INDEX_CONFIG:
index_dir = api_dir / subdir
if index_dir.is_dir():
if update_index_rst(api_dir, subdir, title, intro, modules_heading):
if not args.quiet:
print(f"更新索引: api/{subdir}/index.rst")
if not args.quiet:
print()
# 从 CHANGELOG.md 生成 docs/changelog.rst
docs_dir = repo_root / args.output_dir
if not args.dry_run:
if generate_changelog_rst(repo_root, docs_dir):
if not args.quiet:
print("更新: changelog.rst (来自 CHANGELOG.md)")
if not args.quiet:
print()
if not args.quiet:
print("=" * 50)
print(f"完成! 共处理 {generated} 个文件")
print("=" * 50)
return generated
def main():
parser = argparse.ArgumentParser(description='从 C 头文件生成 RST 文档')
parser.add_argument('--output-dir', '-o', default='docs',
help='输出目录 (默认: docs)')
parser.add_argument('--dry-run', '-n', action='store_true',
help='只显示将要生成的文件,不实际写入')
parser.add_argument('--header', '-H',
help='只处理指定的头文件')
parser.add_argument('--quiet', '-q', action='store_true',
help='静默模式,仅在失败时输出')
args = parser.parse_args()
run(args)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,171 @@
#!/usr/bin/env bash
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$REPO_ROOT"
BUILD_SPHINX=1
BUILD_API_RST=1
BUILD_DOXYGEN=1
while [[ $# -gt 0 ]]; do
case "$1" in
--skip-sphinx)
BUILD_SPHINX=0
;;
--skip-api-rst)
BUILD_API_RST=0
;;
--skip-doxygen)
BUILD_DOXYGEN=0
;;
*)
echo "Unknown option: $1" >&2
exit 1
;;
esac
shift
done
DOC_BUILD_ROOT="docs/_build/html"
DOXYGEN_DIR="${DOC_BUILD_ROOT}/doxygen"
ASSETS_DIR="${DOC_BUILD_ROOT}/assets"
DOXYFILE_PATH="docs/_build/Doxyfile"
mkdir -p "$DOC_BUILD_ROOT" "$ASSETS_DIR" "${DOC_BUILD_ROOT}/en" "${DOC_BUILD_ROOT}/zh_CN" "$(dirname "$DOXYFILE_PATH")"
if [[ "$BUILD_API_RST" -eq 1 ]]; then
echo "[docs] Generating API RST sources..."
python3 docs/scripts/generate_api_docs.py --output-dir docs --quiet
fi
if [[ "$BUILD_SPHINX" -eq 1 ]]; then
echo "[docs] Extracting gettext messages..."
python3 -m sphinx -b gettext -d docs/_build/doctrees-gettext docs docs/_build/gettext
echo "[docs] Building zh_CN message catalogs (.po/.mo)..."
python3 docs/scripts/sync_locale_zh.py
echo "[docs] Building Sphinx HTML (en)..."
python3 -m sphinx -b html -d docs/_build/doctrees-en -D language=en docs "${DOC_BUILD_ROOT}/en"
echo "[docs] Building Sphinx HTML (zh_CN)..."
python3 -m sphinx -b html -d docs/_build/doctrees-zh -D language=zh_CN docs "${DOC_BUILD_ROOT}/zh_CN"
fi
# Root landing: language hub (no mixed-language pages; pick EN or 中文)
cat <<'EOF' > "${DOC_BUILD_ROOT}/index.html"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ESP Emote GFX Documentation</title>
<meta http-equiv="refresh" content="0; url=en/index.html">
<style>
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
margin: 2rem; line-height: 1.6; background: #f7f7f8; color: #1a1a1a; }
a { color: #c41e1a; text-decoration: none; font-weight: 600; }
a:hover { text-decoration: underline; }
ul { padding-left: 1.2rem; }
</style>
</head>
<body>
<p><strong>ESP Emote GFX</strong> — choose documentation language / 选择文档语言:</p>
<ul>
<li><a href="en/index.html">English (EN)</a></li>
<li><a href="zh_CN/index.html">简体中文 (ZH)</a></li>
</ul>
<p>Redirecting to English… / 正在跳转至英文版…</p>
</body>
</html>
EOF
# Create a build-local Doxyfile so docs generation does not touch the repo root.
cat <<'EOF' > "$DOXYFILE_PATH"
PROJECT_NAME = esp_emote_gfx
OUTPUT_DIRECTORY = docs/doxygen_output
GENERATE_HTML = YES
HTML_OUTPUT = html
INPUT = . src include components
FILE_PATTERNS = *.h *.hpp *.c *.cpp
RECURSIVE = YES
EXTRACT_ALL = YES
FULL_PATH_NAMES = NO
GENERATE_LATEX = NO
WARN_IF_UNDOCUMENTED = NO
QUIET = YES
EOF
if [[ "$BUILD_DOXYGEN" -eq 1 ]] && ! command -v doxygen >/dev/null 2>&1; then
echo "Warning: doxygen not found, Doxygen API docs will be skipped"
fi
rm -rf "$DOXYGEN_DIR"
mkdir -p "$DOXYGEN_DIR"
if [[ "$BUILD_DOXYGEN" -eq 1 ]] && command -v doxygen >/dev/null 2>&1; then
doxygen "$DOXYFILE_PATH"
if [ -d docs/doxygen_output/html ]; then
cp -r docs/doxygen_output/html/. "$DOXYGEN_DIR"/
fi
fi
if [ ! -f "$DOXYGEN_DIR/index.html" ]; then
cat <<'EOF' > "$DOXYGEN_DIR/index.html"
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Doxygen API Reference</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; margin: 2rem; line-height: 1.6; background: #f7f7f8; }
a { color: #c41e1a; text-decoration: none; font-weight: 600; }
a:hover { text-decoration: underline; }
</style>
</head>
<body>
<h1>Doxygen API Reference</h1>
<p>Doxygen documentation was not generated. Please check the build logs.</p>
<p><a href="../en/index.html">← English docs</a> · <a href="../zh_CN/index.html">← 中文文档</a></p>
</body>
</html>
EOF
fi
cat <<'EOF' > "$ASSETS_DIR/espidf.css"
:root { --bg:#f7f7f8; --text:#1a1a1a; --accent:#c41e1a; --muted:#6a737d; --border:#d0d4d8; --code-bg:#f0f2f4; }
body { background:var(--bg); color:var(--text); font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Arial,"Noto Sans",sans-serif; }
a { color:var(--accent); text-decoration:none; font-weight:600; } a:hover { text-decoration:underline; }
pre, code { background:var(--code-bg); border:1px solid var(--border); border-radius:4px; padding:.25rem .5rem; }
.header,.headertitle,.navpath,.footer,.memitem,.memdoc,.memberdecls,.directory { border-color:var(--border)!important; }
.memname { font-weight:600; } .mdescLeft,.mdescRight,.qindex { color:var(--muted); }
EOF
if [ -d "$DOXYGEN_DIR" ]; then
cp "$ASSETS_DIR/espidf.css" "$DOXYGEN_DIR/espidf.css"
python3 <<'PY'
import os, io
root = os.path.join("docs", "_build", "html", "doxygen")
css = '<link rel="stylesheet" href="espidf.css" />'
if not os.path.isdir(root):
raise SystemExit(0)
for dirpath, _, files in os.walk(root):
for name in files:
if not name.endswith(".html"):
continue
path = os.path.join(dirpath, name)
with io.open(path, "r", encoding="utf-8", errors="ignore") as fh:
html = fh.read()
if "espidf.css" in html:
continue
html = html.replace("</head>", css + "</head>", 1) if "</head>" in html else css + html
with io.open(path, "w", encoding="utf-8") as fh:
fh.write(html)
PY
fi
echo "Documentation post-processing complete."
echo " - Sphinx EN: ${DOC_BUILD_ROOT}/en/"
echo " - Sphinx ZH: ${DOC_BUILD_ROOT}/zh_CN/"
echo " - Landing: ${DOC_BUILD_ROOT}/index.html"
echo " - Doxygen: ${DOXYGEN_DIR}/"

View File

@ -0,0 +1,67 @@
#!/usr/bin/env python3
"""
Build zh_CN .po/.mo from gettext .pot files + docs/locale/zh_CN/translation_data.py
Requires: Babel (usually installed with Sphinx). Requires docs/_build/gettext/*.pot
(run: sphinx-build -b gettext docs docs/_build/gettext).
"""
from __future__ import annotations
import io
import sys
from pathlib import Path
try:
from babel.messages.mofile import write_mo
from babel.messages.pofile import read_po, write_po
except ImportError as e:
print("Babel is required (pip install Babel).", file=sys.stderr)
raise SystemExit(1) from e
def main() -> int:
script_dir = Path(__file__).resolve().parent
docs_dir = script_dir.parent
repo_root = docs_dir.parent
gettext_dir = docs_dir / "_build" / "gettext"
locale_msgs = docs_dir / "locale" / "zh_CN" / "LC_MESSAGES"
locale_msgs.mkdir(parents=True, exist_ok=True)
sys.path.insert(0, str(docs_dir / "locale" / "zh_CN"))
from translation_data import TRANSLATIONS_BY_CATALOG # type: ignore
if not gettext_dir.is_dir():
print(f"Missing {gettext_dir}; run sphinx-build -b gettext first.", file=sys.stderr)
return 1
for pot_path in sorted(gettext_dir.glob("*.pot")):
name = pot_path.stem
catalog = read_po(io.open(pot_path, encoding="utf-8"))
catalog.locale = "zh_CN"
catalog.fuzzy = False
trans = TRANSLATIONS_BY_CATALOG.get(name, {})
for msg in catalog:
if not msg.id:
continue
if isinstance(msg.id, (list, tuple)):
continue
if msg.id in trans:
msg.string = trans[msg.id]
out_po = locale_msgs / f"{name}.po"
buf_po = io.BytesIO()
write_po(buf_po, catalog, omit_header=False, width=79)
out_po.write_text(buf_po.getvalue().decode("utf-8"), encoding="utf-8")
buf = io.BytesIO()
write_mo(buf, catalog)
mo_path = locale_msgs / f"{name}.mo"
mo_path.write_bytes(buf.getvalue())
print(f" wrote {out_po.relative_to(repo_root)} + .mo")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@ -0,0 +1,28 @@
dependencies:
cmake_utilities:
version: 0.*
espressif/esp_lcd_touch:
public: true
version: '>=1.0'
espressif/esp_new_jpeg:
public: true
version: 1.*
espressif/freetype:
version: 2.*
idf:
version: '>=5.0'
laride/heatshrink:
version: ^0.4.1
lvgl/lvgl:
public: true
version: '*'
description: ESP Emote GFX - A lightweight UI graphics library for compact embedded
displays.
documentation: https://espressif2022.github.io/esp_emote_gfx/en/index.html
issues: https://github.com/espressif2022/esp_emote_gfx/issues
repository: git://github.com/espressif2022/esp_emote_gfx.git
repository_info:
commit_sha: 47f9fd04a44d5d2b5dba8c14f73e4fc0f76b21f9
path: .
url: https://github.com/espressif2022/esp_emote_gfx
version: 3.0.5

View File

@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "sdkconfig.h"
/**
* Fractional bits for mesh vertex coordinates (gfx_mesh_img_point_q8_t x_q8/y_q8)
* and for the software triangle rasterizer.
*/
#define GFX_MESH_FRAC_SHIFT 8
#define GFX_MESH_FRAC_ONE (1 << GFX_MESH_FRAC_SHIFT)
#define GFX_MESH_FRAC_HALF (1 << (GFX_MESH_FRAC_SHIFT - 1))
#define GFX_MESH_FRAC_MASK (GFX_MESH_FRAC_ONE - 1)
/**
* Triangle outer-edge AA distance threshold, same units as vertex coordinates.
* Kconfig 0 means one logical pixel (GFX_MESH_FRAC_ONE).
*/
#if defined(CONFIG_GFX_BLEND_TRI_EDGE_AA_RANGE) && (CONFIG_GFX_BLEND_TRI_EDGE_AA_RANGE > 0)
#define GFX_BLEND_TRI_EDGE_AA_RANGE (CONFIG_GFX_BLEND_TRI_EDGE_AA_RANGE)
#else
#define GFX_BLEND_TRI_EDGE_AA_RANGE (GFX_MESH_FRAC_ONE)
#endif

View File

@ -0,0 +1,93 @@
/*
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include "esp_err.h"
#include "esp_heap_caps.h"
#include "gfx_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* DEFINES
*********************/
/** Use as .task = GFX_EMOTE_INIT_CONFIG() when initializing gfx_core_config_t */
#define GFX_EMOTE_INIT_CONFIG() \
{ \
.task_priority = 4, \
.task_stack = 7168, \
.task_affinity = -1, \
.task_stack_caps = MALLOC_CAP_DEFAULT, \
}
/*********************
* TYPEDEFS
*********************/
/** Passed to gfx_emote_init(); add displays with gfx_disp_add() after init */
typedef struct {
uint32_t fps; /**< Target FPS (frames per second) */
struct {
int task_priority; /**< Render task priority (120) */
int task_stack; /**< Render task stack size (bytes) */
int task_affinity; /**< CPU core (-1: any, 0/1: pinned) */
unsigned task_stack_caps; /**< Stack heap caps (see esp_heap_caps.h) */
} task;
} gfx_core_config_t;
/**********************
* PUBLIC API
**********************/
/**
* @brief Initialize graphics context
*
* @param cfg Core configuration (gfx_core_config_t): fps, task. Add displays with gfx_disp_add() and gfx_disp_config_t.
* @return gfx_handle_t Graphics handle, NULL on error
*
* @note gfx_core_config_t fields: fps, task (priority, stack, affinity, stack_caps).
* Resolution, buffers and flush callback are per-display; see gfx_disp_config_t and gfx_disp_add().
*/
gfx_handle_t gfx_emote_init(const gfx_core_config_t *cfg);
/**
* @brief Deinitialize graphics context
*
* @param handle Graphics handle
*/
void gfx_emote_deinit(gfx_handle_t handle);
/**
* @brief Lock the recursive render mutex to prevent rendering during external operations
*
* @param handle Graphics handle
* @return esp_err_t ESP_OK on success, otherwise an error code
*/
esp_err_t gfx_emote_lock(gfx_handle_t handle);
/**
* @brief Unlock the recursive render mutex after external operations
*
* @param handle Graphics handle
* @return esp_err_t ESP_OK on success, otherwise an error code
*/
esp_err_t gfx_emote_unlock(gfx_handle_t handle);
/**
* @brief Perform one synchronous refresh (render and flush) immediately.
* Holds the render mutex for the duration; safe to call from any task.
*
* @param handle Graphics handle
* @return esp_err_t ESP_OK on success, otherwise an error code
*/
esp_err_t gfx_refr_now(gfx_handle_t handle);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,182 @@
/*
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "esp_err.h"
#include "gfx_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* TYPEDEFS
*********************/
/** Display handle: one per screen; from gfx_disp_add(), use with all gfx_disp_* APIs */
typedef struct gfx_disp gfx_disp_t;
typedef enum {
GFX_DISP_EVENT_IDLE = 0,
GFX_DISP_EVENT_ONE_FRAME_DONE,
GFX_DISP_EVENT_PART_FRAME_DONE,
GFX_DISP_EVENT_ALL_FRAME_DONE,
} gfx_disp_event_t;
typedef struct {
uint64_t calls; /**< Number of API calls */
uint64_t pixels; /**< Processed pixels */
uint64_t time_us; /**< Elapsed time in microseconds */
} gfx_perf_counter_t;
typedef struct {
gfx_perf_counter_t fill; /**< gfx_sw_blend_fill_area */
gfx_perf_counter_t color_draw; /**< gfx_sw_blend_draw */
gfx_perf_counter_t image_draw; /**< gfx_sw_blend_img_draw */
gfx_perf_counter_t triangle_draw; /**< gfx_sw_blend_img_triangle_draw */
uint64_t triangle_covered_pixels; /**< Triangle pixels blended (inside + AA) */
uint64_t triangle_aa_pixels; /**< Triangle edge-AA blended pixels */
} gfx_blend_perf_stats_t;
typedef struct {
uint32_t dirty_pixels; /**< Dirty pixels in the latest rendered frame */
uint64_t frame_time_us; /**< Total frame time */
uint64_t render_time_us; /**< Time spent in render phase */
uint64_t flush_time_us; /**< Time spent in flush callbacks */
uint32_t flush_count; /**< Number of flush calls */
gfx_blend_perf_stats_t blend; /**< Blend-stage details */
} gfx_disp_perf_stats_t;
typedef void (*gfx_disp_flush_cb_t)(gfx_disp_t *disp, int x1, int y1, int x2, int y2, const void *data);
typedef void (*gfx_disp_update_cb_t)(gfx_disp_t *disp, gfx_disp_event_t event, const void *obj);
/*********************
* CONFIG STRUCTS
*********************/
/** Passed to gfx_disp_add() for multi-screen setup */
typedef struct {
uint32_t h_res; /**< Screen width in pixels */
uint32_t v_res; /**< Screen height in pixels */
gfx_disp_flush_cb_t flush_cb; /**< Flush callback for this display */
gfx_disp_update_cb_t update_cb; /**< Update callback (frame/playback events) */
void *user_data; /**< User data for this display */
struct {
unsigned char swap : 1; /**< Color swap flag */
unsigned char buff_dma : 1; /**< Alloc buffer with MALLOC_CAP_DMA (internal alloc only) */
unsigned char buff_spiram : 1; /**< Alloc buffer in PSRAM (internal alloc only) */
unsigned char double_buffer : 1; /**< Alloc second buffer for double buffering (internal alloc only) */
unsigned char full_frame : 1; /**< 1 = buf1/buf2 are full-screen framebuffers (e.g. RGB); draw at chunk region. 0 = partition buffer; draw from start. */
} flags;
struct {
void *buf1; /**< Frame buffer 1 (NULL = internal alloc) */
void *buf2; /**< Frame buffer 2 (NULL = internal alloc) */
size_t buf_pixels; /**< Size per buffer in pixels (0 = auto) */
} buffers;
} gfx_disp_config_t;
/**********************
* PUBLIC API
**********************/
/**
* @brief Add a display (multi-screen support)
*
* @param handle Graphics handle from gfx_emote_init
* @param cfg Display configuration (resolution, flush callback, buffers)
* @return gfx_disp_t* New display pointer on success, NULL on error
*/
gfx_disp_t *gfx_disp_add(gfx_handle_t handle, const gfx_disp_config_t *cfg);
/**
* @brief Remove a display from the list and release its resources (child list nodes, event group, buffers).
* Does not free the gfx_disp_t; caller must free(disp) after.
*
* @param disp Display from gfx_disp_add; safe to pass NULL
*/
void gfx_disp_del(gfx_disp_t *disp);
/**
* @brief Invalidate full screen of a display to trigger refresh
*
* @param disp Display from gfx_disp_add
*/
void gfx_disp_refresh_all(gfx_disp_t *disp);
/**
* @brief Notify that flush is done (e.g. from panel IO callback)
*
* @param disp Display from gfx_disp_add
* @param swap_act_buf Whether to swap the active buffer
* @return bool True on success
*/
bool gfx_disp_flush_ready(gfx_disp_t *disp, bool swap_act_buf);
/**
* @brief Get user data for a display
*
* @param disp Display from gfx_disp_add
* @return void* User data, or NULL
*/
void *gfx_disp_get_user_data(gfx_disp_t *disp);
/**
* @brief Get display horizontal resolution in pixels
*
* @param disp Display from gfx_disp_add (NULL allowed; returns default width)
* @return uint32_t Width in pixels
*/
uint32_t gfx_disp_get_hor_res(gfx_disp_t *disp);
/**
* @brief Get display vertical resolution in pixels
*
* @param disp Display from gfx_disp_add (NULL allowed; returns default height)
* @return uint32_t Height in pixels
*/
uint32_t gfx_disp_get_ver_res(gfx_disp_t *disp);
/**
* @brief Check if display is currently flushing the last block
*
* @param disp Display from gfx_disp_add
* @return true if flushing last block, false otherwise
*/
bool gfx_disp_is_flushing_last(gfx_disp_t *disp);
/**
* @brief Get latest per-display performance statistics
*
* Stats are updated once a frame is rendered. If no frame has rendered yet,
* all fields remain zero.
*
* @param disp Display handle
* @param out_stats Output stats structure
* @return ESP_OK on success
*/
esp_err_t gfx_disp_get_perf_stats(gfx_disp_t *disp, gfx_disp_perf_stats_t *out_stats);
/**
* @brief Set default background color for a display
*
* @param disp Display from gfx_disp_add
* @param color Background color (e.g. RGB565)
* @return esp_err_t ESP_OK on success
*/
esp_err_t gfx_disp_set_bg_color(gfx_disp_t *disp, gfx_color_t color);
/**
* @brief Enable or disable drawing the background (fill with bg_color before widgets)
*
* @param disp Display from gfx_disp_add
* @param enable true to enable background (default), false to disable background
* @return ESP_OK on success
*/
esp_err_t gfx_disp_set_bg_enable(gfx_disp_t *disp, bool enable);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,68 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include <stdarg.h>
#ifdef __cplusplus
extern "C" {
#endif
/**********************
* TYPEDEFS
**********************/
typedef enum {
GFX_LOG_LEVEL_NONE = 0,
GFX_LOG_LEVEL_ERROR,
GFX_LOG_LEVEL_WARN,
GFX_LOG_LEVEL_INFO,
GFX_LOG_LEVEL_DEBUG,
GFX_LOG_LEVEL_VERBOSE,
} gfx_log_level_t;
typedef enum {
GFX_LOG_MODULE_CORE = 0,
GFX_LOG_MODULE_DISP,
GFX_LOG_MODULE_OBJ,
GFX_LOG_MODULE_REFR,
GFX_LOG_MODULE_RENDER,
GFX_LOG_MODULE_TIMER,
GFX_LOG_MODULE_TOUCH,
GFX_LOG_MODULE_IMG_DEC,
GFX_LOG_MODULE_LABEL,
GFX_LOG_MODULE_LABEL_OBJ,
GFX_LOG_MODULE_DRAW_LABEL,
GFX_LOG_MODULE_FONT_LV,
GFX_LOG_MODULE_FONT_FT,
GFX_LOG_MODULE_IMG,
GFX_LOG_MODULE_QRCODE,
GFX_LOG_MODULE_BUTTON,
GFX_LOG_MODULE_ANIM,
GFX_LOG_MODULE_ANIM_DEC,
GFX_LOG_MODULE_MOTION,
GFX_LOG_MODULE_EAF_DEC,
GFX_LOG_MODULE_QRCODE_LIB,
GFX_LOG_MODULE_COUNT,
} gfx_log_module_t;
/**********************
* PUBLIC API
**********************/
void gfx_log_set_level(gfx_log_module_t module, gfx_log_level_t level);
gfx_log_level_t gfx_log_get_level(gfx_log_module_t module);
void gfx_log_set_level_all(gfx_log_level_t level);
bool gfx_log_should_output(gfx_log_module_t module, gfx_log_level_t level);
const char *gfx_log_module_name(gfx_log_module_t module);
void gfx_log_write(gfx_log_module_t module, gfx_log_level_t level, const char *tag, const char *format, ...);
void gfx_log_writev(gfx_log_module_t module, gfx_log_level_t level, const char *tag, const char *format, va_list args);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,200 @@
/*
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_err.h"
#include "gfx_types.h"
#include "core/gfx_disp.h"
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* DEFINES
*********************/
/* Object types */
#define GFX_OBJ_TYPE_SCREEN 0x00 /**< Screen type (reserved) */
#define GFX_OBJ_TYPE_IMAGE 0x01
#define GFX_OBJ_TYPE_LABEL 0x02
#define GFX_OBJ_TYPE_ANIMATION 0x03
#define GFX_OBJ_TYPE_QRCODE 0x04
#define GFX_OBJ_TYPE_BUTTON 0x05
#define GFX_OBJ_TYPE_MESH_IMAGE 0x06
#define GFX_OBJ_TYPE_LIST 0x07
#define GFX_OBJ_TYPE_FACE_EMOTE 0x08
/* 0x09 reserved for removed dragon emote */
#define GFX_OBJ_TYPE_LOBSTER_EMOTE 0x0A
/* 0x0B reserved for removed lobster face emote */
#define GFX_OBJ_TYPE_STICKMAN_EMOTE 0x0C
/* Alignment constants (similar to LVGL) */
#define GFX_ALIGN_DEFAULT 0x00
#define GFX_ALIGN_TOP_LEFT 0x00
#define GFX_ALIGN_TOP_MID 0x01
#define GFX_ALIGN_TOP_RIGHT 0x02
#define GFX_ALIGN_LEFT_MID 0x03
#define GFX_ALIGN_CENTER 0x04
#define GFX_ALIGN_RIGHT_MID 0x05
#define GFX_ALIGN_BOTTOM_LEFT 0x06
#define GFX_ALIGN_BOTTOM_MID 0x07
#define GFX_ALIGN_BOTTOM_RIGHT 0x08
#define GFX_ALIGN_OUT_TOP_LEFT 0x09
#define GFX_ALIGN_OUT_TOP_MID 0x0A
#define GFX_ALIGN_OUT_TOP_RIGHT 0x0B
#define GFX_ALIGN_OUT_LEFT_TOP 0x0C
#define GFX_ALIGN_OUT_LEFT_MID 0x0D
#define GFX_ALIGN_OUT_LEFT_BOTTOM 0x0E
#define GFX_ALIGN_OUT_RIGHT_TOP 0x0F
#define GFX_ALIGN_OUT_RIGHT_MID 0x10
#define GFX_ALIGN_OUT_RIGHT_BOTTOM 0x11
#define GFX_ALIGN_OUT_BOTTOM_LEFT 0x12
#define GFX_ALIGN_OUT_BOTTOM_MID 0x13
#define GFX_ALIGN_OUT_BOTTOM_RIGHT 0x14
/**********************
* TYPEDEFS
**********************/
/* Opaque object type - actual definition in gfx_obj_priv.h */
typedef struct gfx_obj gfx_obj_t;
typedef struct gfx_touch_event gfx_touch_event_t;
/**
* @brief Application-level touch callback (register with gfx_obj_set_touch_cb)
* @param obj Object that received the touch
* @param event Touch event (PRESS / RELEASE / MOVE)
* @param user_data User data passed to gfx_obj_set_touch_cb
*/
typedef void (*gfx_obj_touch_cb_t)(gfx_obj_t *obj, const gfx_touch_event_t *event, void *user_data);
/**********************
* PUBLIC API
**********************/
/**
* @brief Set the position of an object
* @param obj Pointer to the object
* @param x X coordinate
* @param y Y coordinate
*/
esp_err_t gfx_obj_set_pos(gfx_obj_t *obj, gfx_coord_t x, gfx_coord_t y);
/**
* @brief Set the size of an object
* @param obj Pointer to the object
* @param w Width
* @param h Height
*/
esp_err_t gfx_obj_set_size(gfx_obj_t *obj, uint16_t w, uint16_t h);
/**
* @brief Align an object relative to the screen or another object
* @param obj Pointer to the object to align
* @param align Alignment type (see GFX_ALIGN_* constants)
* @param x_ofs X offset from the alignment position
* @param y_ofs Y offset from the alignment position
*/
esp_err_t gfx_obj_align(gfx_obj_t *obj, uint8_t align, gfx_coord_t x_ofs, gfx_coord_t y_ofs);
/**
* @brief Align an object relative to another object
* @param obj Pointer to the object to align
* @param base Reference object; NULL means align to the display
* @param align Alignment type (see GFX_ALIGN_* constants)
* @param x_ofs X offset from the alignment position
* @param y_ofs Y offset from the alignment position
* @return ESP_OK on success
*/
esp_err_t gfx_obj_align_to(gfx_obj_t *obj, gfx_obj_t *base, uint8_t align, gfx_coord_t x_ofs, gfx_coord_t y_ofs);
/**
* @brief Set object visibility
* @param obj Object to set visibility for
* @param visible True to make object visible, false to hide
*/
esp_err_t gfx_obj_set_visible(gfx_obj_t *obj, bool visible);
/**
* @brief Get object visibility
* @param obj Object to check visibility for
* @return True if object is visible, false if hidden
*/
bool gfx_obj_get_visible(gfx_obj_t *obj);
/**
* @brief Update object's layout (mark for recalculation before rendering)
* @param obj Object to update layout
* @note This is used when object properties that affect layout have changed,
* but the actual position calculation needs to be deferred until rendering
*/
void gfx_obj_update_layout(gfx_obj_t *obj);
/* Object getters */
/**
* @brief Get the position of an object
* @param obj Pointer to the object
* @param x Pointer to store X coordinate
* @param y Pointer to store Y coordinate
*/
esp_err_t gfx_obj_get_pos(gfx_obj_t *obj, gfx_coord_t *x, gfx_coord_t *y);
/**
* @brief Get the size of an object
* @param obj Pointer to the object
* @param w Pointer to store width
* @param h Pointer to store height
*/
esp_err_t gfx_obj_get_size(gfx_obj_t *obj, uint16_t *w, uint16_t *h);
/* Object management */
/**
* @brief Delete an object
* @param obj Pointer to the object to delete
*/
esp_err_t gfx_obj_delete(gfx_obj_t *obj);
/**
* @brief Register application touch callback for an object
*
* When this object is the hit target of a touch (PRESS/MOVE/RELEASE), the callback
* is invoked. Pass NULL to unregister.
*
* @param obj Object to listen on
* @param cb Callback (NULL to clear)
* @param user_data Passed to cb
* @return ESP_OK on success
*/
esp_err_t gfx_obj_set_touch_cb(gfx_obj_t *obj, gfx_obj_touch_cb_t cb, void *user_data);
/**
* @brief Get object creation sequence id (monotonic per process lifetime)
* @param obj Object pointer
* @return uint32_t Sequence id, 0 if obj is NULL
*/
uint32_t gfx_obj_get_trace_id(gfx_obj_t *obj);
/**
* @brief Get object class name (from registered widget class metadata)
* @param obj Object pointer
* @return const char* Class name string, or NULL
*/
const char *gfx_obj_get_class_name(gfx_obj_t *obj);
/**
* @brief Get object creation tag (creation-site annotation)
* @param obj Object pointer
* @return const char* Creation tag string, or NULL
*/
const char *gfx_obj_get_trace_tag(gfx_obj_t *obj);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,109 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "gfx_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/* Timer callback function type */
typedef void (*gfx_timer_cb_t)(void *);
/* Timer handle type for external use */
typedef void *gfx_timer_handle_t;
/**********************
* GLOBAL PROTOTYPES
**********************/
/*=====================
* Timer functions
*====================*/
/**
* @brief Create a new timer
* @param handle Player handle
* @param timer_cb Timer callback function
* @param period Timer period in milliseconds
* @param user_data User data passed to callback
* @return Timer handle, NULL on error
*/
gfx_timer_handle_t gfx_timer_create(void *handle, gfx_timer_cb_t timer_cb, uint32_t period, void *user_data);
/**
* @brief Delete a timer
* @param handle Player handle
* @param timer Timer handle to delete
*/
void gfx_timer_delete(void *handle, gfx_timer_handle_t timer);
/**
* @brief Pause a timer
* @param timer Timer handle to pause
*/
void gfx_timer_pause(gfx_timer_handle_t timer);
/**
* @brief Resume a timer
* @param timer Timer handle to resume
*/
void gfx_timer_resume(gfx_timer_handle_t timer);
/**
* @brief Check if a timer is running
* @param timer_handle Timer handle to check
* @return true if timer is running, false otherwise
*/
bool gfx_timer_is_running(gfx_timer_handle_t timer_handle);
/**
* @brief Set timer repeat count
* @param timer Timer handle to modify
* @param repeat_count Number of times to repeat (-1 for infinite)
*/
void gfx_timer_set_repeat_count(gfx_timer_handle_t timer, int32_t repeat_count);
/**
* @brief Set timer period
* @param timer Timer handle to modify
* @param period New period in milliseconds
*/
void gfx_timer_set_period(gfx_timer_handle_t timer, uint32_t period);
/**
* @brief Reset a timer
* @param timer Timer handle to reset
*/
void gfx_timer_reset(gfx_timer_handle_t timer);
/**
* @brief Get current system tick
* @return Current tick value in milliseconds
*/
uint32_t gfx_timer_tick_get(void);
/**
* @brief Get actual FPS from timer manager
* @param handle Player handle
* @return Actual FPS value, 0 if handle is invalid
*/
uint32_t gfx_timer_get_actual_fps(void *handle);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,84 @@
/*
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#include "esp_lcd_touch.h"
#include "core/gfx_disp.h"
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* TYPEDEFS
*********************/
/** Touch handle: from gfx_touch_add(), pass to event_cb and other touch APIs */
typedef struct gfx_touch gfx_touch_t;
typedef enum {
GFX_TOUCH_EVENT_PRESS = 0,
GFX_TOUCH_EVENT_RELEASE,
GFX_TOUCH_EVENT_MOVE, /**< Finger moved while pressed (slide) */
} gfx_touch_event_type_t;
/** Payload passed to gfx_touch_event_cb_t; hit_obj is set when touch is bound to a disp */
typedef struct gfx_touch_event {
gfx_touch_event_type_t type;
uint16_t x;
uint16_t y;
uint16_t strength;
uint8_t track_id;
uint32_t timestamp_ms;
} gfx_touch_event_t;
typedef void (*gfx_touch_event_cb_t)(gfx_touch_t *touch, const gfx_touch_event_t *event, void *user_data);
/** Passed to gfx_touch_add(); NULL or no handle disables touch */
typedef struct {
esp_lcd_touch_handle_t handle; /**< LCD touch driver handle */
gfx_touch_event_cb_t event_cb; /**< Event callback */
uint32_t poll_ms; /**< Poll interval ms (0 = default) */
gfx_disp_t *disp; /**< Display handle */
void *user_data; /**< User data for callback */
} gfx_touch_config_t;
/**********************
* PUBLIC API
**********************/
/**
* @brief Add a touch device (like gfx_disp_add; multiple touch devices supported)
*
* @param handle Graphics handle from gfx_emote_init
* @param cfg Touch configuration (handle, poll_ms, event_cb, etc.); required
* @return gfx_touch_t* Touch pointer on success, NULL on error
*/
gfx_touch_t *gfx_touch_add(gfx_handle_t handle, const gfx_touch_config_t *cfg);
/**
* @brief Bind a display to a touch device
*
* @param touch Touch pointer returned from gfx_touch_add
* @param disp Display to receive touch hit-testing and dispatch
* @return ESP_OK on success, ESP_ERR_INVALID_ARG if touch is NULL
*/
esp_err_t gfx_touch_set_disp(gfx_touch_t *touch, gfx_disp_t *disp);
/**
* @brief Remove a touch device from the list and release resources (stops polling, disables IRQ).
* Does not free the gfx_touch_t; caller must free(touch) after.
*
* @param touch Touch pointer returned from gfx_touch_add; safe to pass NULL
*/
void gfx_touch_del(gfx_touch_t *touch);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,117 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* DEFINES
*********************/
/* Pixel size constants */
#define GFX_PIXEL_SIZE_16BPP 2 /**< 16-bit color format: 2 bytes per pixel */
#define GFX_PIXEL_SIZE_8BPP 1 /**< 8-bit format: 1 byte per pixel */
/**
* @brief Calculate buffer pointer with offset for 16-bit format (RGB565)
* @param buffer Base buffer pointer (any type)
* @param y_offset Vertical offset in pixels
* @param stride Width of buffer in pixels
* @param x_offset Horizontal offset in pixels
* @return Calculated gfx_color_t pointer with offset applied
*/
#define GFX_BUFFER_OFFSET_16BPP(buffer, y_offset, stride, x_offset) \
((uint8_t *)((uint8_t *)(buffer) + \
(y_offset) * (stride) * GFX_PIXEL_SIZE_16BPP + \
(x_offset) * GFX_PIXEL_SIZE_16BPP))
/**
* @brief Calculate buffer pointer with offset for 8-bit format
* @param buffer Base buffer pointer (any type)
* @param y_offset Vertical offset in pixels
* @param stride Width of buffer in pixels
* @param x_offset Horizontal offset in pixels
* @return Calculated uint8_t pointer with offset applied
*/
#define GFX_BUFFER_OFFSET_8BPP(buffer, y_offset, stride, x_offset) \
((uint8_t *)((uint8_t *)(buffer) + \
(y_offset) * (stride) * GFX_PIXEL_SIZE_8BPP + \
(x_offset) * GFX_PIXEL_SIZE_8BPP))
/**
* @brief Calculate buffer pointer with offset for 4-bit format (2 pixels per byte)
* @param buffer Base buffer pointer (any type)
* @param y_offset Vertical offset in pixels
* @param stride Width of buffer in pixels (will be divided by 2)
* @param x_offset Horizontal offset in pixels (will be divided by 2)
* @return Calculated uint8_t pointer with offset applied
*/
#define GFX_BUFFER_OFFSET_4BPP(buffer, y_offset, stride, x_offset) \
((uint8_t *)((uint8_t *)(buffer) + \
(y_offset) * ((stride) / 2) + \
(x_offset) / 2))
#define GFX_COLOR_HEX(color) ((gfx_color_t)gfx_color_hex(color))
/**********************
* TYPEDEFS
**********************/
/* Basic types */
typedef uint8_t gfx_opa_t; /**< Opacity (0-255) */
typedef int16_t gfx_coord_t; /**< Coordinate type */
/** Graphics handle type */
typedef void *gfx_handle_t; /**< Graphics handle type */
/* Color type with full member for compatibility */
typedef union {
uint16_t full; /**< Full 16-bit color value */
} gfx_color_t;
/* Area structure */
typedef struct {
gfx_coord_t x1;
gfx_coord_t y1;
gfx_coord_t x2;
gfx_coord_t y2;
} gfx_area_t;
/**********************
* PUBLIC API
**********************/
/**
* @brief Convert a 32-bit hexadecimal color to gfx_color_t
* @param c The 32-bit hexadecimal color to convert
* @return Converted color in gfx_color_t type
*/
gfx_color_t gfx_color_hex(uint32_t c);
/**
* @brief Convert a semantic gfx_color_t to native framebuffer order.
*
* Use this helper only when writing raw 16-bit pixels directly into a buffer
* or calling raw fill helpers that do not accept a separate `swap` argument.
*
* @param color Semantic RGB565 color value.
* @param swap Whether the target buffer expects swapped byte order.
* @return Native-order 16-bit pixel value for the target buffer.
*/
static inline uint16_t gfx_color_to_native_u16(gfx_color_t color, bool swap)
{
return swap ? (uint16_t)__builtin_bswap16(color.full) : color.full;
}
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,40 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "core/gfx_types.h"
#include "core/gfx_core.h"
#include "core/gfx_disp.h"
#include "core/gfx_log.h"
#include "core/gfx_timer.h"
#include "core/gfx_touch.h"
#include "core/gfx_obj.h"
#include "widget/gfx_img.h"
#include "widget/gfx_mesh_img.h"
#include "widget/gfx_motion.h"
#include "widget/gfx_motion_scene.h"
#include "widget/gfx_qrcode.h"
#include "widget/gfx_label.h"
#include "widget/gfx_button.h"
#include "widget/gfx_anim.h"
#include "widget/gfx_font_lvgl.h"
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,199 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stddef.h>
#include "core/gfx_obj.h"
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
typedef enum {
GFX_ANIM_SEGMENT_ACTION_CONTINUE = 0,
GFX_ANIM_SEGMENT_ACTION_PAUSE,
} gfx_anim_segment_action_t;
/**
* @brief Playback description for one animation segment.
*
* A segment defines:
* - frame range
* - playback speed
* - total repeat count
* - what to do when the segment finishes
*
* Use `gfx_anim_set_segment()` for the simple single-segment case.
* Use `gfx_anim_set_segments()` when you need a playback plan.
*/
typedef struct {
uint32_t start; /* inclusive start frame */
uint32_t end; /* inclusive end frame */
uint32_t fps; /* playback fps for this segment */
uint32_t play_count; /* total plays for this segment, 0 means forever */
gfx_anim_segment_action_t end_action; /* action after the last play finishes */
} gfx_anim_segment_t;
/**
* @brief Public animation source type.
*
* The current implementation supports in-memory animation payloads.
* The enum exists so future source types can be added without changing the
* source-setting API shape again.
*/
typedef enum {
GFX_ANIM_SRC_TYPE_MEMORY = 0, /**< In-memory animation payload */
} gfx_anim_src_type_t;
/**
* @brief Typed animation source descriptor.
*
* `gfx_anim_set_src_desc()` is the preferred source setter for new code.
* `gfx_anim_set_src()` remains as a compatibility wrapper for raw memory
* buffers and length pairs.
*/
typedef struct {
gfx_anim_src_type_t type; /**< Source payload type */
const void *data; /**< Type-specific payload pointer */
size_t data_len; /**< Payload length in bytes */
} gfx_anim_src_t;
/**********************
* PUBLIC API
**********************/
/**
* @brief Create an animation object on a display
* @param disp Display from gfx_emote_add_disp(handle, &disp_cfg)
* @return Pointer to the created animation object
*/
gfx_obj_t *gfx_anim_create(gfx_disp_t *disp);
/* Animation setters */
/**
* @brief Set the typed source descriptor for an animation object
*
* This is the preferred source setter for new code.
*
* @param obj Pointer to the animation object
* @param src Pointer to the typed source descriptor
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_anim_set_src_desc(gfx_obj_t *obj, const gfx_anim_src_t *src);
/**
* @brief Set the source data for an animation object
*
* Compatibility wrapper for in-memory animation payloads.
*
* @param obj Pointer to the animation object
* @param src_data Source data
* @param src_len Source data length
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_anim_set_src(gfx_obj_t *obj, const void *src_data, size_t src_len);
/**
* @brief Set the segment for an animation object
* @param obj Pointer to the animation object
* @param start Start frame index
* @param end End frame index
* @param fps Frames per second
* @param repeat Whether to repeat the animation
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_anim_set_segment(gfx_obj_t *obj, uint32_t start, uint32_t end, uint32_t fps, bool repeat);
/**
* @brief Set a segment playback plan for an animation object
* @param obj Pointer to the animation object
* @param segments Segment plan array
* @param segment_count Number of segment entries in the array
*
* Each segment uses `play_count` to describe the total number of plays:
* - `play_count = 1`: play once
* - `play_count = N`: play N times
* - `play_count = 0`: play forever
*
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_anim_set_segments(gfx_obj_t *obj, const gfx_anim_segment_t *segments, size_t segment_count);
/**
* @brief Drain the remaining segment plan and block until playback finishes
*
* This API is intended for segment-plan mode. If playback is currently inside
* a loop phase of the active segment, or paused after a segment `end_action`,
* calling this API will drain the remaining plan exactly once:
* - the active segment continues from its current frame to its end
* - the active segment's remaining repeat count is ignored
* - all following segments are played once in order
* - pause actions in the remaining plan are ignored
*
* The function blocks until the remaining plan has finished.
*
* Do not call this API while holding the graphics lock.
*
* @param obj Pointer to the animation object
* @return ESP_OK on success, ESP_ERR_NOT_FOUND if there is no remaining work,
* or another ESP_ERR_* code on failure
*/
esp_err_t gfx_anim_play_left_to_tail(gfx_obj_t *obj);
/**
* @brief Start the animation
* @param obj Pointer to the animation object
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_anim_start(gfx_obj_t *obj);
/**
* @brief Stop the animation
* @param obj Pointer to the animation object
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_anim_stop(gfx_obj_t *obj);
/**
* @brief Set mirror display for an animation object
*
* Manual mirror duplicates the rendered image horizontally and inserts the
* provided offset between the original and mirrored copy.
* For display-width-aware mirroring, use `gfx_anim_set_auto_mirror()`.
*
* @param obj Pointer to the animation object
* @param enabled Whether to enable mirror display
* @param offset Mirror offset in pixels
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_anim_set_mirror(gfx_obj_t *obj, bool enabled, int16_t offset);
/**
* @brief Set auto mirror alignment for animation object
*
* Auto mirror computes the mirror offset from the current display width.
* Compared with `gfx_anim_set_mirror()`, this mode is easier to use when the
* animation should mirror around the display center without a fixed offset.
*
* @param obj Animation object
* @param enabled Whether to enable auto mirror alignment
* @return ESP_OK on success, ESP_ERR_* otherwise
*/
esp_err_t gfx_anim_set_auto_mirror(gfx_obj_t *obj, bool enabled);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,96 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "widget/gfx_label.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Create a button object on a display
* @param disp Display from gfx_disp_add()
* @return Pointer to the created button object
*/
gfx_obj_t *gfx_button_create(gfx_disp_t *disp);
/**
* @brief Set the label text for a button
* @param obj Button object
* @param text Text string; NULL is treated as an empty string
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_button_set_text(gfx_obj_t *obj, const char *text);
/**
* @brief Set the label text for a button using printf-style formatting
* @param obj Button object
* @param fmt Format string
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_button_set_text_fmt(gfx_obj_t *obj, const char *fmt, ...);
/**
* @brief Set the font used by the button label
* @param obj Button object
* @param font Font handle
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_button_set_font(gfx_obj_t *obj, gfx_font_t font);
/**
* @brief Set the label text color for a button
* @param obj Button object
* @param color Text color
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_button_set_text_color(gfx_obj_t *obj, gfx_color_t color);
/**
* @brief Set the normal background color for a button
* @param obj Button object
* @param color Background color
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_button_set_bg_color(gfx_obj_t *obj, gfx_color_t color);
/**
* @brief Set the pressed background color for a button
* @param obj Button object
* @param color Pressed background color
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_button_set_bg_color_pressed(gfx_obj_t *obj, gfx_color_t color);
/**
* @brief Set the border color for a button
* @param obj Button object
* @param color Border color
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_button_set_border_color(gfx_obj_t *obj, gfx_color_t color);
/**
* @brief Set the border width for a button
* @param obj Button object
* @param width Border width in pixels; 0 disables the border
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_button_set_border_width(gfx_obj_t *obj, uint16_t width);
/**
* @brief Set the text alignment for a button label
* @param obj Button object
* @param align Text alignment
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_button_set_text_align(gfx_obj_t *obj, gfx_text_align_t align);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,52 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
/*********************
* INCLUDES
*********************/
#include <stdint.h>
#include "lvgl.h"
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* PUBLIC API
**********************/
/*
* The following code (gfx_font_lv_load_from_binary and gfx_font_lv_delete)
* is derived from 78/xiaozhi-fonts project.
* Original source: https://github.com/78/xiaozhi-fonts
*/
/**
* @brief Load an LVGL font from binary data
* @param bin_addr Pointer to binary data containing lv_font_t structure
* @return Pointer to loaded lv_font_t, or NULL on failure
*/
lv_font_t *gfx_font_lv_load_from_binary(uint8_t *bin_addr);
/**
* @brief Delete an LVGL font created from binary data
* @param font Pointer to lv_font_t to delete
*/
void gfx_font_lv_delete(lv_font_t *font);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,112 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "core/gfx_obj.h"
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* DEFINES
*********************/
/* Magic numbers for image headers */
#define C_ARRAY_HEADER_MAGIC 0x19
/**********************
* TYPEDEFS
**********************/
/* Color format enumeration - simplified for public use */
typedef enum {
GFX_COLOR_FORMAT_RGB565 = 0x04, /**< RGB565 format without alpha channel */
GFX_COLOR_FORMAT_RGB565A8 = 0x0A, /**< RGB565 format with separate alpha channel */
} gfx_color_format_t;
typedef struct {
uint32_t magic: 8; /**< Magic number. Must be GFX_IMAGE_HEADER_MAGIC */
uint32_t cf : 8; /**< Color format: See `gfx_color_format_t` */
uint32_t flags: 16; /**< Image flags */
uint32_t w: 16; /**< Width of the image */
uint32_t h: 16; /**< Height of the image */
uint32_t stride: 16; /**< Number of bytes in a row */
uint32_t reserved: 16; /**< Reserved for future use */
} gfx_image_header_t;
/* Image descriptor structure - compatible with LVGL */
typedef struct {
gfx_image_header_t header; /**< A header describing the basics of the image */
uint32_t data_size; /**< Size of the image in bytes */
const uint8_t *data; /**< Pointer to the data of the image */
const void *reserved; /**< Reserved field for future use */
const void *reserved_2; /**< Reserved field for future use */
} gfx_image_dsc_t;
/**
* @brief Public image source type.
*
* Use this enum together with `gfx_img_src_t` to describe where an image
* payload comes from. The current implementation supports in-memory
* `gfx_image_dsc_t` payloads and keeps room for future source types.
*/
typedef enum {
GFX_IMG_SRC_TYPE_IMAGE_DSC = 0, /**< In-memory gfx_image_dsc_t payload */
} gfx_img_src_type_t;
/**
* @brief Typed image source descriptor.
*
* `gfx_img_set_src_desc()` is the preferred API because it makes the source
* type explicit. `gfx_img_set_src()` remains as a compatibility wrapper for
* direct `gfx_image_dsc_t *` usage.
*/
typedef struct {
gfx_img_src_type_t type; /**< Source payload type */
const void *data; /**< Type-specific payload pointer */
} gfx_img_src_t;
/**********************
* PUBLIC API
**********************/
/**
* @brief Create an image object on a display
* @param disp Display from gfx_emote_add_disp(handle, &disp_cfg)
* @return Pointer to the created image object, NULL on error
*/
gfx_obj_t *gfx_img_create(gfx_disp_t *disp);
/* Image setters */
/**
* @brief Set the typed source descriptor for an image object
*
* This is the preferred source setter for new code. It keeps the public API
* extensible when additional image source types are introduced.
*
* @param obj Pointer to the image object
* @param src Pointer to the typed source descriptor
* @return ESP_OK on success, ESP_ERR_* otherwise
*/
esp_err_t gfx_img_set_src_desc(gfx_obj_t *obj, const gfx_img_src_t *src);
/**
* @brief Set the source data for an image object
*
* Compatibility wrapper for in-memory `gfx_image_dsc_t` payloads.
*
* @param obj Pointer to the image object
* @param src Pointer to the image source data
* @return ESP_OK on success, ESP_ERR_* otherwise
*/
esp_err_t gfx_img_set_src(gfx_obj_t *obj, void *src);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,215 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stddef.h>
#include "sdkconfig.h"
#include "core/gfx_obj.h"
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* DEFINES
*********************/
/* Font handle type - hides internal FreeType implementation */
typedef void *gfx_font_t;
/**********************
* TYPEDEFS
**********************/
/**
* Text alignment enumeration (similar to LVGL)
*/
typedef enum {
GFX_TEXT_ALIGN_AUTO, /**< Align text auto */
GFX_TEXT_ALIGN_LEFT, /**< Align text to left */
GFX_TEXT_ALIGN_CENTER, /**< Align text to center */
GFX_TEXT_ALIGN_RIGHT, /**< Align text to right */
} gfx_text_align_t;
/**
* Long text mode enumeration (similar to LVGL)
*/
typedef enum {
GFX_LABEL_LONG_WRAP, /**< Break the long lines (word wrap) */
GFX_LABEL_LONG_SCROLL, /**< Make the text scrolling horizontally smoothly */
GFX_LABEL_LONG_CLIP, /**< Simply clip the parts which don't fit */
GFX_LABEL_LONG_SCROLL_SNAP, /**< Jump to next section after interval (horizontal paging) */
} gfx_label_long_mode_t;
typedef struct {
const char *name; /**< The name of the font file */
const void *mem; /**< The pointer to the font file */
size_t mem_size; /**< The size of the memory */
uint16_t font_size; /**< The size of the font */
} gfx_label_cfg_t;
/**********************
* PUBLIC API
**********************/
/**
* @brief Create a label object on a display
* @param disp Display from gfx_emote_add_disp(handle, &disp_cfg)
* @return Pointer to the created label object
*/
gfx_obj_t *gfx_label_create(gfx_disp_t *disp);
#ifdef CONFIG_GFX_FONT_FREETYPE_SUPPORT
/* Font management */
/**
* @brief Create a new font
* @param cfg Font configuration
* @param ret_font Pointer to store the font handle
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_label_new_font(const gfx_label_cfg_t *cfg, gfx_font_t *ret_font);
/**
* @brief Delete a font and free its resources
* @param font Font handle to delete
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_label_delete_font(gfx_font_t font);
#endif
/* Label setters */
/**
* @brief Set the text for a label object
* @param obj Pointer to the label object
* @param text Text string to display
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_label_set_text(gfx_obj_t *obj, const char *text);
/**
* @brief Set the text for a label object with format
* @param obj Pointer to the label object
* @param fmt Format string
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_label_set_text_fmt(gfx_obj_t *obj, const char *fmt, ...);
/**
* @brief Set the color for a label object
* @param obj Pointer to the label object
* @param color Color value
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_label_set_color(gfx_obj_t *obj, gfx_color_t color);
/**
* @brief Set the background color for a label object
* @param obj Pointer to the label object
* @param bg_color Background color value
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_label_set_bg_color(gfx_obj_t *obj, gfx_color_t bg_color);
/**
* @brief Enable or disable background for a label object
* @param obj Pointer to the label object
* @param enable True to enable background, false to disable
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_label_set_bg_enable(gfx_obj_t *obj, bool enable);
/**
* @brief Set the opacity for a label object
* @param obj Pointer to the label object
* @param opa Opacity value (0-255)
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_label_set_opa(gfx_obj_t *obj, gfx_opa_t opa);
/**
* @brief Set the font for a label object
* @param obj Pointer to the label object
* @param font Font handle
*/
esp_err_t gfx_label_set_font(gfx_obj_t *obj, gfx_font_t font);
/**
* @brief Set the text alignment for a label object
* @param obj Pointer to the label object
* @param align Text alignment value
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_label_set_text_align(gfx_obj_t *obj, gfx_text_align_t align);
/**
* @brief Set the long text mode for a label object
* @param obj Pointer to the label object
* @param long_mode Long text handling mode (wrap, scroll, or clip)
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_label_set_long_mode(gfx_obj_t *obj, gfx_label_long_mode_t long_mode);
/**
* @brief Set the line spacing for a label object
* @param obj Pointer to the label object
* @param spacing Line spacing in pixels
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_label_set_line_spacing(gfx_obj_t *obj, uint16_t spacing);
/**
* @brief Set the horizontal scrolling speed for a label object
* @param obj Pointer to the label object
* @param speed_ms Scrolling speed in milliseconds per pixel
* @note Only effective when long_mode is GFX_LABEL_LONG_SCROLL
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_label_set_scroll_speed(gfx_obj_t *obj, uint32_t speed_ms);
/**
* @brief Set whether scrolling should loop continuously
* @param obj Pointer to the label object
* @param loop True to enable continuous looping, false for one-time scroll
* @note Only effective when long_mode is GFX_LABEL_LONG_SCROLL
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_label_set_scroll_loop(gfx_obj_t *obj, bool loop);
/**
* @brief Set the scroll step size for a label object
* @param obj Pointer to the label object
* @param step Scroll step size in pixels per timer tick (default: 1, can be negative)
* @note Only effective when long_mode is GFX_LABEL_LONG_SCROLL
* @note Step cannot be zero
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_label_set_scroll_step(gfx_obj_t *obj, int32_t step);
/**
* @brief Set the snap scroll interval time for a label object
* @param obj Pointer to the label object
* @param interval_ms Interval time in milliseconds to stay on each section before jumping
* @note Only effective when long_mode is GFX_LABEL_LONG_SCROLL_SNAP
* @note The jump offset is automatically calculated as the label width
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_label_set_snap_interval(gfx_obj_t *obj, uint32_t interval_ms);
/**
* @brief Set whether snap scrolling should loop continuously
* @param obj Pointer to the label object
* @param loop True to enable continuous looping, false to stop at end
* @note Only effective when long_mode is GFX_LABEL_LONG_SCROLL_SNAP
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_label_set_snap_loop(gfx_obj_t *obj, bool loop);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,313 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stddef.h>
#include "core/gfx_obj.h"
#include "widget/gfx_img.h"
#ifdef __cplusplus
extern "C" {
#endif
/**********************
* TYPEDEFS
**********************/
/**
* @brief Mesh control point in integer pixel coordinates.
*
* Use this type when callers only need whole-pixel positioning.
* For subpixel precision, use `gfx_mesh_img_point_q8_t`.
*/
typedef struct {
gfx_coord_t x;
gfx_coord_t y;
} gfx_mesh_img_point_t;
/**
* @brief Mesh control point in Q8 fixed-point coordinates.
*
* This variant is intended for subpixel-accurate deformation and animation.
* Compared with `gfx_mesh_img_point_t`, it preserves 1/256 pixel precision.
*/
typedef struct {
int32_t x_q8;
int32_t y_q8;
} gfx_mesh_img_point_q8_t;
/**********************
* PUBLIC API
**********************/
/**
* @brief Create a mesh-image object on a display.
*
* A mesh-image widget deforms an image through a regular point grid.
* Compared with `gfx_img_create()`, this widget supports per-point warp.
*
* @param disp Display that owns the object
* @return Created object, or NULL on failure
*/
gfx_obj_t *gfx_mesh_img_create(gfx_disp_t *disp);
/**
* @brief Set a typed image source descriptor for the mesh.
*
* This is the preferred source setter for new code. It keeps the source type
* explicit and aligned with the image widget API.
*
* @param obj Mesh-image object
* @param src Typed image source descriptor
* @return ESP_OK on success, ESP_ERR_* otherwise
*/
esp_err_t gfx_mesh_img_set_src_desc(gfx_obj_t *obj, const gfx_img_src_t *src);
/**
* @brief Set the image source for the mesh.
*
* Compatibility wrapper for direct `gfx_image_dsc_t *` payloads.
*
* @param obj Mesh-image object
* @param src In-memory image source payload
* @return ESP_OK on success, ESP_ERR_* otherwise
*/
esp_err_t gfx_mesh_img_set_src(gfx_obj_t *obj, void *src);
/**
* @brief Configure mesh grid density.
*
* `cols` and `rows` describe the number of cells, not points.
* The actual point count is `(cols + 1) * (rows + 1)`.
*
* @param obj Mesh-image object
* @param cols Horizontal cell count
* @param rows Vertical cell count
* @return ESP_OK on success, ESP_ERR_* otherwise
*/
esp_err_t gfx_mesh_img_set_grid(gfx_obj_t *obj, uint8_t cols, uint8_t rows);
/**
* @brief Get the current mesh point count.
*
* This reflects the current grid configuration.
*
* @param obj Mesh-image object
* @return Number of points in the current mesh
*/
size_t gfx_mesh_img_get_point_count(gfx_obj_t *obj);
/**
* @brief Get one mesh point in object-local pixel coordinates.
*
* Compared with `gfx_mesh_img_get_point_screen()`, this returns coordinates
* relative to the mesh object itself.
*
* @param obj Mesh-image object
* @param point_idx Point index in the current grid
* @param point Output point
* @return ESP_OK on success, ESP_ERR_* otherwise
*/
esp_err_t gfx_mesh_img_get_point(gfx_obj_t *obj, size_t point_idx, gfx_mesh_img_point_t *point);
/**
* @brief Get one mesh point in screen coordinates.
*
* Compared with `gfx_mesh_img_get_point()`, this includes the current object
* position and alignment result.
*
* @param obj Mesh-image object
* @param point_idx Point index in the current grid
* @param x Output screen x
* @param y Output screen y
* @return ESP_OK on success, ESP_ERR_* otherwise
*/
esp_err_t gfx_mesh_img_get_point_screen(gfx_obj_t *obj, size_t point_idx, gfx_coord_t *x, gfx_coord_t *y);
/**
* @brief Get one mesh point in object-local Q8 coordinates.
*
* Compared with `gfx_mesh_img_get_point()`, this preserves subpixel precision.
*
* @param obj Mesh-image object
* @param point_idx Point index in the current grid
* @param point Output Q8 point
* @return ESP_OK on success, ESP_ERR_* otherwise
*/
esp_err_t gfx_mesh_img_get_point_q8(gfx_obj_t *obj, size_t point_idx, gfx_mesh_img_point_q8_t *point);
/**
* @brief Get one mesh point in screen-space Q8 coordinates.
*
* This combines the current object position with the point's subpixel value.
*
* @param obj Mesh-image object
* @param point_idx Point index in the current grid
* @param x_q8 Output screen x in Q8
* @param y_q8 Output screen y in Q8
* @return ESP_OK on success, ESP_ERR_* otherwise
*/
esp_err_t gfx_mesh_img_get_point_screen_q8(gfx_obj_t *obj, size_t point_idx, int32_t *x_q8, int32_t *y_q8);
/**
* @brief Set one mesh point in object-local pixel coordinates.
*
* For subpixel updates, use `gfx_mesh_img_set_point_q8()`.
*
* @param obj Mesh-image object
* @param point_idx Point index in the current grid
* @param x Local x
* @param y Local y
* @return ESP_OK on success, ESP_ERR_* otherwise
*/
esp_err_t gfx_mesh_img_set_point(gfx_obj_t *obj, size_t point_idx, gfx_coord_t x, gfx_coord_t y);
/**
* @brief Set all mesh points in object-local pixel coordinates.
*
* The caller must provide exactly the current point count.
* For subpixel updates, use `gfx_mesh_img_set_points_q8()`.
*
* @param obj Mesh-image object
* @param points Point array
* @param point_count Number of entries in `points`
* @return ESP_OK on success, ESP_ERR_* otherwise
*/
esp_err_t gfx_mesh_img_set_points(gfx_obj_t *obj, const gfx_mesh_img_point_t *points, size_t point_count);
/**
* @brief Set one mesh point in object-local Q8 coordinates.
*
* Compared with `gfx_mesh_img_set_point()`, this keeps subpixel precision.
*
* @param obj Mesh-image object
* @param point_idx Point index in the current grid
* @param x_q8 Local x in Q8
* @param y_q8 Local y in Q8
* @return ESP_OK on success, ESP_ERR_* otherwise
*/
esp_err_t gfx_mesh_img_set_point_q8(gfx_obj_t *obj, size_t point_idx, int32_t x_q8, int32_t y_q8);
/**
* @brief Set all mesh points in object-local Q8 coordinates.
*
* This is the preferred batch API for smooth deformation animation.
*
* @param obj Mesh-image object
* @param points Q8 point array
* @param point_count Number of entries in `points`
* @return ESP_OK on success, ESP_ERR_* otherwise
*/
esp_err_t gfx_mesh_img_set_points_q8(gfx_obj_t *obj, const gfx_mesh_img_point_q8_t *points, size_t point_count);
/**
* @brief Set the rest pose points in object-local pixel coordinates.
*
* Rest points define the undeformed reference mesh used for texture sampling
* and later reset operations. Compared with `gfx_mesh_img_set_points()`, this
* updates the reference pose instead of only the current deformation.
*
* @param obj Mesh-image object
* @param points Point array
* @param point_count Number of entries in `points`
* @return ESP_OK on success, ESP_ERR_* otherwise
*/
esp_err_t gfx_mesh_img_set_rest_points(gfx_obj_t *obj, const gfx_mesh_img_point_t *points, size_t point_count);
/**
* @brief Set the rest pose points in object-local Q8 coordinates.
*
* Compared with `gfx_mesh_img_set_rest_points()`, this keeps subpixel
* precision in the reference pose.
*
* @param obj Mesh-image object
* @param points Q8 point array
* @param point_count Number of entries in `points`
* @return ESP_OK on success, ESP_ERR_* otherwise
*/
esp_err_t gfx_mesh_img_set_rest_points_q8(gfx_obj_t *obj, const gfx_mesh_img_point_q8_t *points, size_t point_count);
/**
* @brief Reset current points back to the stored rest pose.
*
* @param obj Mesh-image object
* @return ESP_OK on success, ESP_ERR_* otherwise
*/
esp_err_t gfx_mesh_img_reset_points(gfx_obj_t *obj);
/**
* @brief Show or hide mesh control points for debugging.
*
* This affects only debug visualization and does not change the deformation.
*
* @param obj Mesh-image object
* @param visible Whether control points should be drawn
* @return ESP_OK on success, ESP_ERR_* otherwise
*/
esp_err_t gfx_mesh_img_set_ctrl_points_visible(gfx_obj_t *obj, bool visible);
/**
* @brief Set uniform mesh opacity.
*
* This value is multiplied with any source alpha and anti-aliasing coverage.
*
* @param obj Mesh image object
* @param opa Uniform opacity (0-255)
* @return ESP_OK on success, ESP_ERR_* otherwise
*/
esp_err_t gfx_mesh_img_set_opa(gfx_obj_t *obj, gfx_opa_t opa);
/**
* @brief Enable inward-only edge anti-aliasing.
*
* When enabled, outer edges of this mesh fade from full opacity to transparent
* towards the geometric boundary (inside the triangle) instead of drawing
* semi-transparent pixels outside. Prevents visible "bleed" on thin strokes.
*
* @param obj Mesh image object.
* @param inward true = inward AA (no outward bleed); false = default outward AA.
* @return ESP_OK on success, ESP_ERR_* otherwise
*/
esp_err_t gfx_mesh_img_set_aa_inward(gfx_obj_t *obj, bool inward);
/**
* @brief Treat first and last grid columns as adjacent (closed strip).
*
* When enabled, the left edge of the first column and the right edge of the
* last column are marked as internal (shared), so edge AA does not fade them
* to transparent. Use for closed stroke paths where the strip endpoints
* coincide geometrically.
*
* Compared with `gfx_mesh_img_set_aa_inward()`, this changes edge topology
* interpretation rather than AA direction.
*
* @return ESP_OK on success, ESP_ERR_* otherwise
*/
esp_err_t gfx_mesh_img_set_wrap_cols(gfx_obj_t *obj, bool wrap);
/**
* @brief Use scanline polygon fill instead of triangle rasterization.
*
* When enabled (grid_rows must be 1), the mesh outline is filled as a closed
* polygon using a scanline rasterizer with edge AA. No texture mapping
* fills with a solid color. Avoids diagonal-seam artifacts inherent in
* per-triangle inward AA.
*
* Compared with the default textured-triangle mode, this is intended for
* stroke-like meshes where a solid-color fill is preferable.
*
* @param obj Mesh image object
* @param enable Whether scanline fill mode should be enabled
* @param fill_color Solid fill color (typically white for strokes).
* @return ESP_OK on success, ESP_ERR_* otherwise
*/
esp_err_t gfx_mesh_img_set_scanline_fill(gfx_obj_t *obj, bool enable, gfx_color_t fill_color);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,70 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "esp_err.h"
#include "core/gfx_obj.h"
#include "core/gfx_timer.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* Lightweight motion driver.
*
* Goal: let higher-level scene/player code focus on state changes only.
* - The driver owns a timer and calls `tick_cb` periodically.
* - When `tick_cb` reports changes (or `force_apply`), the driver calls `apply_cb`.
*/
typedef struct gfx_motion_cfg_t {
uint16_t timer_period_ms;
int16_t damping_div;
} gfx_motion_cfg_t;
typedef struct gfx_motion_t gfx_motion_t;
typedef bool (*gfx_motion_tick_cb_t)(gfx_motion_t *motion, void *user_data);
typedef esp_err_t (*gfx_motion_apply_cb_t)(gfx_motion_t *motion, void *user_data, bool force_apply);
struct gfx_motion_t {
gfx_timer_handle_t timer;
gfx_motion_cfg_t cfg;
gfx_disp_t *disp;
gfx_obj_t *anchor;
gfx_motion_tick_cb_t tick_cb;
gfx_motion_apply_cb_t apply_cb;
void *user_data;
};
void gfx_motion_cfg_init(gfx_motion_cfg_t *cfg, uint16_t timer_period_ms, int16_t damping_div);
esp_err_t gfx_motion_init(gfx_motion_t *motion,
gfx_disp_t *disp,
gfx_obj_t *anchor,
const gfx_motion_cfg_t *cfg,
gfx_motion_tick_cb_t tick_cb,
gfx_motion_apply_cb_t apply_cb,
void *user_data);
void gfx_motion_deinit(gfx_motion_t *motion);
esp_err_t gfx_motion_set_period(gfx_motion_t *motion, uint16_t period_ms);
/** Run one tick immediately (no wait). */
esp_err_t gfx_motion_step(gfx_motion_t *motion, bool force_apply);
/** Utility: damped step for int16 values (same policy as existing widgets). */
int16_t gfx_motion_ease_i16(int16_t cur, int16_t tgt, int16_t div);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,383 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
/**
* @file gfx_motion_scene.h
*
* Motion Scene Asset three-layer architecture:
*
* Layer 1 GENERATED ASSET (firmware-side):
* Named control points grouped into visual parts via segments. The public
* field names still use joint_* for ABI compatibility, but semantically they
* are generic control points: skeleton endpoints, Bézier controls, or mesh
* anchors depending on the segment kind.
* All emote types (stickman, face, lobster-style textured) share the same
* Motion Scene Asset layout:
*
* Segment kind Control points Rendered as
*
* CAPSULE joint_a, joint_b Thick capsule (limb / body segment)
* RING joint_a Hollow ring (head)
* BEZIER_STRIP joint_a .. +n-1 Open thick Bézier curve (brow)
* BEZIER_LOOP joint_a .. +n-1 Closed thick Bézier loop (mouth outline)
* BEZIER_FILL joint_a .. +n-1 Closed fill: n=7 eye, n=13 ellipse quad, else any n=3k+1 (hub mesh)
*
* Stickman: each control point = one skeleton endpoint.
* Face: each control point = one cubic Bézier point (n = 3k+1 format).
* Textured: any segment can reference a ROM image via segment.resource_idx.
* Poses store the *actual* target positions (pre-blended for face expressions).
*
* Layer 2 PARSER (gfx_motion_scene.c):
* Validates asset, manages runtime pose_cur / pose_tgt interpolation, and
* advances action timelines. Zero display calls.
*
* Layer 3 RUNTIME (gfx_motion_player.c):
* Creates one gfx_mesh_img per segment. On every sync it maps design-space
* pose_cur[] to screen pixels and calls the appropriate primitive helper
* (capsule / ring / bezier) based on segment kind.
* No type flag required segment kind encodes everything.
*/
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "esp_err.h"
#include "core/gfx_obj.h"
#include "widget/gfx_img.h"
#include "widget/gfx_motion.h"
#ifdef __cplusplus
extern "C" {
#endif
#define GFX_MOTION_SCENE_SCHEMA_VERSION 2U
/* ------------------------------------------------------------------ */
/* 0. Resource table (textures / image assets) */
/* ------------------------------------------------------------------ */
/**
* One entry in the asset's resource table.
*
* A segment with resource_idx > 0 uses resources[resource_idx - 1]
* as its mesh_img texture source instead of the runtime solid colour.
* This allows texture-mapped segments (e.g. lobster body) to live in
* the same unified asset format as solid-colour vector segments.
*
* resource_idx = 0 solid colour (default, zero-init compatible)
* resource_idx = N resources[N-1]
*/
typedef struct {
const gfx_image_dsc_t *image; /**< Pointer to the image descriptor (ROM .inc array) */
uint16_t uv_x; /**< Source crop origin X (0 = full image) */
uint16_t uv_y; /**< Source crop origin Y (0 = full image) */
uint16_t uv_w; /**< Source crop width (0 = image width from uv_x) */
uint16_t uv_h; /**< Source crop height (0 = image height from uv_y) */
} gfx_motion_resource_t;
/* ------------------------------------------------------------------ */
/* 1. Segment primitives */
/* ------------------------------------------------------------------ */
/**
* Primitive kind determines how the renderer draws a segment.
*
* CAPSULE / RING use control points as endpoint pair / center.
* BEZIER_STRIP / BEZIER_LOOP / BEZIER_FILL use a contiguous range of
* control points as cubic Bézier controls (n = 3k+1 polygon format).
*/
typedef enum {
GFX_MOTION_SEG_CAPSULE = 0, /**< Thick capsule between joint_a → joint_b */
GFX_MOTION_SEG_RING = 1, /**< Hollow ring centred at joint_a */
GFX_MOTION_SEG_BEZIER_STRIP = 2, /**< Open thick Bézier curve (e.g. brow) */
GFX_MOTION_SEG_BEZIER_LOOP = 3, /**< Closed thick Bézier loop (e.g. mouth outline) */
GFX_MOTION_SEG_BEZIER_FILL = 4, /**< Closed filled Bézier shape (e.g. eye sclera) */
} gfx_motion_segment_kind_t;
/** One visual part wiring control points to a rendering primitive. */
typedef struct {
gfx_motion_segment_kind_t kind;
uint16_t joint_a; /**< CAPSULE: start; RING: centre; BEZIER: first ctrl pt */
uint16_t joint_b; /**< CAPSULE: end ; unused for RING/BEZIER */
uint16_t joint_count; /**< BEZIER_*: number of consecutive control points (n=3k+1) */
uint8_t stroke_width; /**< Design-space override; 0 = use layout->stroke_width */
uint8_t layer_bit; /**< Visibility layer mask bit (0 = always shown) */
int16_t radius_hint; /**< RING: design-space radius */
/**
* Texture / resource binding.
* 0 = solid colour (driven by gfx_motion_player_set_color).
* N>0 = use asset->resources[N-1] as the mesh_img image source.
*/
uint8_t resource_idx;
/**
* Palette colour index.
* 0 = use runtime colour (gfx_motion_player_set_color), not affected by set_color.
* N>0 = use asset->color_palette[N-1] (0xRRGGBB) as the fixed segment colour.
* set_color() skips palette-coloured segments.
*/
uint8_t color_idx;
/**
* Segment opacity 0-255.
* 0 is treated as 255 (fully opaque) for zero-init compatibility.
*/
uint8_t opacity;
} gfx_motion_segment_t;
/* ------------------------------------------------------------------ */
/* 2. Poses — flat arrays of control point coordinates */
/* ------------------------------------------------------------------ */
/**
* One pose: flat [x0,y0, x1,y1, ] array, length = joint_count × 2.
* joint_count is the ABI field name; conceptually it is the number of
* generated control points.
* For stickman: x,y = skeleton endpoint position in design space.
* For face: x,y = Bézier control point position in design space
* (pre-blended from reference shapes + expression weights).
*/
typedef struct {
const int16_t *coords;
} gfx_motion_pose_t;
/* ------------------------------------------------------------------ */
/* 3. Actions (animation sequences) */
/* ------------------------------------------------------------------ */
/** Interpolation style when transitioning into an action step. */
typedef enum {
GFX_MOTION_INTERP_HOLD = 0, /**< Snap immediately to target pose */
GFX_MOTION_INTERP_DAMPED = 1, /**< Exponential ease (damping_div) */
} gfx_motion_interp_t;
/** One step in an action: selects a target pose and how long to hold it. */
typedef struct {
uint16_t pose_index; /**< Index into gfx_motion_asset_t.poses[] */
uint16_t hold_ticks; /**< Timer ticks to hold before advancing */
gfx_motion_interp_t interp; /**< Transition style into this step */
int8_t facing; /**< 1=right -1=left (mirrors X) */
} gfx_motion_action_step_t;
/** Animation action: a sequence of steps with loop control. */
typedef struct {
const gfx_motion_action_step_t *steps;
uint8_t step_count;
bool loop;
} gfx_motion_action_t;
/* ------------------------------------------------------------------ */
/* 4. Metadata and layout hints */
/* ------------------------------------------------------------------ */
typedef struct {
uint32_t version; /**< Must equal GFX_MOTION_SCENE_SCHEMA_VERSION */
int32_t viewbox_x;
int32_t viewbox_y;
int32_t viewbox_w;
int32_t viewbox_h;
} gfx_motion_meta_t;
/**
* Rendering parameters. Separated from geometry so they can be
* overridden without touching the ROM asset.
*/
typedef struct {
int16_t stroke_width; /**< Default capsule / Bézier stroke thickness (design units) */
int16_t mirror_x; /**< X axis for facing=-1 horizontal mirroring */
int16_t ground_y; /**< Informational floor position */
uint16_t timer_period_ms; /**< Action-advance timer period */
int16_t damping_div; /**< Divisor for INTERP_DAMPED easing (1 = snap) */
} gfx_motion_layout_t;
/* ------------------------------------------------------------------ */
/* 5. Top-level asset bundle */
/* ------------------------------------------------------------------ */
typedef struct {
const gfx_motion_meta_t *meta;
/** Control point name table (joint_count entries; field name kept for ABI). */
const char *const *joint_names;
uint16_t joint_count;
/** Segment wiring (segment_count entries; 0 is valid). */
const gfx_motion_segment_t *segments;
uint8_t segment_count;
/** Pose library. */
const gfx_motion_pose_t *poses;
uint16_t pose_count;
/** Action library. */
const gfx_motion_action_t *actions;
uint16_t action_count;
/** Default playback sequence (action indices). */
const uint16_t *sequence;
uint16_t sequence_count;
/** Rendering hints. */
const gfx_motion_layout_t *layout;
/**
* Optional texture/image resource table.
* Segments reference entries here via segment.resource_idx (1-based).
* NULL and resource_count=0 are valid (all segments use solid colour).
*/
const gfx_motion_resource_t *resources;
uint8_t resource_count;
/**
* Optional per-segment colour palette.
* Stored as 0xRRGGBB 24-bit values; converted to native pixel at runtime init.
* Segments reference entries via segment.color_idx (1-based).
* NULL and color_palette_count=0 are valid (all non-resource segments use
* the runtime colour set by gfx_motion_player_set_color).
*/
const uint32_t *color_palette;
uint8_t color_palette_count;
} gfx_motion_asset_t;
/* ------------------------------------------------------------------ */
/* Layer 2 — PARSER runtime state */
/* ------------------------------------------------------------------ */
/**
* Maximum total control points per asset.
* Raised beyond 512 so closed-loop rigs can duplicate outline control points
* for BEZIER_FILL companions without immediately exhausting the budget.
*/
#define GFX_MOTION_SCENE_MAX_POINTS 640U
/**
* Maximum control points in a single BEZIER_* segment.
* Shared by scene/player code so invalid assets fail early at compile/import time.
*/
#define GFX_MOTION_SCENE_MAX_SEG_CTRL_POINTS 64U
typedef struct {
int16_t x;
int16_t y;
} gfx_motion_point_t;
typedef struct {
const gfx_motion_asset_t *asset;
gfx_motion_point_t pose_cur[GFX_MOTION_SCENE_MAX_POINTS]; /**< Current (animated) positions */
gfx_motion_point_t pose_tgt[GFX_MOTION_SCENE_MAX_POINTS]; /**< Target positions */
uint16_t active_action;
uint8_t active_step;
uint16_t step_ticks;
bool action_loop_override_en;
bool action_loop_override;
bool dirty;
} gfx_motion_scene_t;
esp_err_t gfx_motion_scene_init(gfx_motion_scene_t *scene, const gfx_motion_asset_t *asset);
esp_err_t gfx_motion_scene_set_action(gfx_motion_scene_t *scene, uint16_t action_index, bool snap_now);
esp_err_t gfx_motion_scene_set_action_loop(gfx_motion_scene_t *scene, bool loop);
esp_err_t gfx_motion_scene_clear_action_loop_override(gfx_motion_scene_t *scene);
/** Ease pose_cur toward pose_tgt one tick. Returns true if any coord changed. */
bool gfx_motion_scene_tick(gfx_motion_scene_t *scene);
/** Advance the action timeline (hold_ticks countdown and step transitions). */
void gfx_motion_scene_advance(gfx_motion_scene_t *scene);
/**
* Debug: print active action index, step index, pose index, hold ticks, facing, and interp.
* Generated Motion Scene Assets expose action enums in their .inc files; the
* parser only sees numeric action/pose indices at runtime.
*/
void gfx_motion_scene_log_active_step(const gfx_motion_scene_t *scene, const char *reason);
/* ------------------------------------------------------------------ */
/* Layer 3 — RUNTIME (unified renderer) */
/* ------------------------------------------------------------------ */
/** Maximum mesh_img objects per runtime (one per segment). */
#define GFX_MOTION_PLAYER_MAX_SEGMENTS 64U
/** Maximum colour palette entries (colour_idx 1..GFX_MOTION_PALETTE_MAX). */
#define GFX_MOTION_PALETTE_MAX 16U
/**
* Unified animation runtime.
*
* Owns a gfx_motion_scene_t (scene state) + gfx_motion_t (timer driver) + one gfx_mesh_img
* per segment. Dispatches rendering based on segment kind no separate
* "stickman renderer" vs "face renderer".
*
* Usage:
* gfx_motion_player_t player = {0};
* gfx_motion_player_init(&player, disp, &my_asset);
* gfx_motion_player_set_color(&player, GFX_COLOR_HEX(0xFFFFFF));
* gfx_motion_player_set_action(&player, action_index, false);
*/
typedef struct {
gfx_motion_scene_t scene;
gfx_motion_t motion;
/* ── private ── */
gfx_obj_t *seg_objs[GFX_MOTION_PLAYER_MAX_SEGMENTS]; /**< One mesh_img per segment */
uint8_t seg_grid_cols[GFX_MOTION_PLAYER_MAX_SEGMENTS];
uint8_t seg_grid_rows[GFX_MOTION_PLAYER_MAX_SEGMENTS];
uint8_t seg_obj_count;
gfx_color_t stroke_color;
uint32_t layer_mask;
uint16_t solid_pixel;
gfx_image_dsc_t solid_img;
/** Per-palette-entry native pixels and their 1×1 image descriptors. */
uint16_t palette_pixels[GFX_MOTION_PALETTE_MAX];
gfx_image_dsc_t palette_imgs[GFX_MOTION_PALETTE_MAX];
gfx_coord_t canvas_x;
gfx_coord_t canvas_y;
uint16_t canvas_w;
uint16_t canvas_h;
bool mesh_dirty;
void *scratch;
} gfx_motion_player_t;
/**
* Initialise the player: parse the asset, create mesh objects, and start the motion timer.
* Canvas defaults to full display; override with gfx_motion_player_set_canvas().
*/
esp_err_t gfx_motion_player_init(gfx_motion_player_t *player,
gfx_disp_t *disp,
const gfx_motion_asset_t *asset);
/** Destroy all mesh_img objects and stop the motion timer. */
void gfx_motion_player_deinit(gfx_motion_player_t *player);
/** Change the stroke colour for all segments. */
esp_err_t gfx_motion_player_set_color(gfx_motion_player_t *player, gfx_color_t color);
/** Override the canvas region the scene is scaled into. */
esp_err_t gfx_motion_player_set_canvas(gfx_motion_player_t *player,
gfx_coord_t x, gfx_coord_t y,
uint16_t w, uint16_t h);
/**
* Set the visible segment layer mask.
*
* Segment layer_bit == 0 is always visible. Segment layer_bit N (1..32)
* is visible when BIT(N - 1) is set in layer_mask.
*/
esp_err_t gfx_motion_player_set_layer_mask(gfx_motion_player_t *player, uint32_t layer_mask);
/** Force the current player state to be applied immediately without advancing time. */
esp_err_t gfx_motion_player_sync(gfx_motion_player_t *player);
/** Switch to an action by index. */
esp_err_t gfx_motion_player_set_action(gfx_motion_player_t *player, uint16_t action_idx, bool snap);
esp_err_t gfx_motion_player_set_action_loop(gfx_motion_player_t *player, bool loop);
esp_err_t gfx_motion_player_clear_action_loop_override(gfx_motion_player_t *player);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,89 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "core/gfx_obj.h"
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**
* QR Code error correction level
*/
typedef enum {
GFX_QRCODE_ECC_LOW = 0, /**< The QR Code can tolerate about 7% erroneous codewords */
GFX_QRCODE_ECC_MEDIUM, /**< The QR Code can tolerate about 15% erroneous codewords */
GFX_QRCODE_ECC_QUARTILE, /**< The QR Code can tolerate about 25% erroneous codewords */
GFX_QRCODE_ECC_HIGH /**< The QR Code can tolerate about 30% erroneous codewords */
} gfx_qrcode_ecc_t;
/**********************
* PUBLIC API
**********************/
/**
* @brief Create a QR Code object on a display
* @param disp Display from gfx_emote_add_disp(handle, &disp_cfg)
* @return Pointer to the created QR Code object
*/
gfx_obj_t *gfx_qrcode_create(gfx_disp_t *disp);
/* QR code setters */
/**
* @brief Set the data/text for a QR Code object
* @param obj Pointer to the QR Code object
* @param data Pointer to the null-terminated string to encode
* @return ESP_OK on success, error code otherwise
* @note The length is automatically calculated using strlen()
*/
esp_err_t gfx_qrcode_set_data(gfx_obj_t *obj, const char *data);
/**
* @brief Set the size for a QR Code object
* @param obj Pointer to the QR Code object
* @param size Size in pixels (both width and height)
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_qrcode_set_size(gfx_obj_t *obj, uint16_t size);
/**
* @brief Set the error correction level for a QR Code object
* @param obj Pointer to the QR Code object
* @param ecc Error correction level
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_qrcode_set_ecc(gfx_obj_t *obj, gfx_qrcode_ecc_t ecc);
/**
* @brief Set the foreground color for a QR Code object
* @param obj Pointer to the QR Code object
* @param color Foreground color (QR modules color)
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_qrcode_set_color(gfx_obj_t *obj, gfx_color_t color);
/**
* @brief Set the background color for a QR Code object
* @param obj Pointer to the QR Code object
* @param bg_color Background color
* @return ESP_OK on success, error code otherwise
*/
esp_err_t gfx_qrcode_set_bg_color(gfx_obj_t *obj, gfx_color_t bg_color);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,222 @@
# Image Converter
将图片转换为 GFX 库支持的格式RGB565/RGB565A8的工具脚本。
## 功能特性
- ✅ 支持 RGB565 格式(无透明通道,节省 33% 内存)
- ✅ 支持 RGB565A8 格式(带独立 alpha 通道)
- ✅ 生成 C 文件或二进制文件
- ✅ 支持字节交换(适配不同硬件)
- ✅ 批量转换整个目录
## 格式对比
| 格式 | 文件大小 (64×64) | 透明支持 | 适用场景 |
|-----------|------------------|----------|------------------------|
| RGB565 | 8 KB | ❌ | 不透明图标、背景图 |
| RGB565A8 | 12 KB | ✅ | 需要透明效果的图标、UI |
## 安装依赖
```bash
pip install Pillow
```
## 使用方法
### 基础用法
```bash
# 转换为 RGB565A8 格式(默认,带透明通道)
python3 image_converter.py image.png
# 转换为 RGB565 格式(无透明通道,更小)
python3 image_converter.py image.png --format rgb565
```
### 高级选项
```bash
# 指定输出目录
python3 image_converter.py image.png --output ./output/
# 生成二进制文件(.bin而不是 C 文件
python3 image_converter.py image.png --bin
# 启用字节交换(某些硬件需要)
python3 image_converter.py image.png --swap16
# 批量转换目录下所有 PNG 文件
python3 image_converter.py ./images/ --output ./converted/
# 组合使用RGB565 + 二进制 + 字节交换
python3 image_converter.py icon.png --format rgb565 --bin --swap16
```
### 完整参数说明
| 参数 | 说明 | 默认值 |
|-------------------------|----------------------------------------|-------------|
| `input` | 输入文件或目录路径 | 必需 |
| `-o, --output` | 输出目录 | 当前目录 |
| `-f, --format` | 输出格式:`rgb565``rgb565a8` | `rgb565a8` |
| `--bin` | 生成二进制文件而不是 C 文件 | 关闭 |
| `--swap16` | 启用 RGB565 字节交换 | 关闭 |
## 输出示例
### C 文件输出 (默认)
```c
#include "gfx.h"
const uint8_t my_icon_map[] = {
0xff, 0xff, 0xff, 0xff, ...
};
const gfx_image_dsc_t my_icon = {
.header.cf = GFX_COLOR_FORMAT_RGB565, // 或 GFX_COLOR_FORMAT_RGB565A8
.header.magic = C_ARRAY_HEADER_MAGIC,
.header.w = 64,
.header.h = 64,
.data_size = 8192, // RGB565: width*height*2, RGB565A8: width*height*3
.data = my_icon_map,
};
```
### 二进制文件输出 (--bin)
```
[12 bytes header]
[image data]
Header 结构:
- magic (0x19)
- cf (0x04=RGB565, 0x0A=RGB565A8)
- width, height
- stride
```
## 使用示例
### 示例 1: 创建不透明图标
```bash
# 转换不需要透明的图标,节省内存
python3 image_converter.py logo.png --format rgb565
```
生成的 C 文件可以这样使用:
```c
#include "logo.c"
gfx_obj_t *img = gfx_img_create(handle);
gfx_img_set_src(img, (void *)&logo);
gfx_obj_align(img, GFX_ALIGN_CENTER, 0, 0);
```
### 示例 2: 创建带透明效果的 UI 元素
```bash
# 转换需要透明效果的图标
python3 image_converter.py button.png --format rgb565a8
```
### 示例 3: 批量转换资源目录
```bash
# 转换 assets 目录下所有 PNG 为 RGB565 格式
python3 image_converter.py ./assets/ \
--format rgb565 \
--output ./src/images/
```
### 示例 4: 为特定硬件生成二进制文件
```bash
# 生成字节交换的二进制文件
python3 image_converter.py icon.png \
--format rgb565 \
--bin \
--swap16 \
--output ./flash_data/
```
## 数据布局
### RGB565 格式
```
[RGB565 pixel data]
- Size: width × height × 2 bytes
```
### RGB565A8 格式
```
[RGB565 pixel data] [Alpha mask data]
- RGB565 size: width × height × 2 bytes
- Alpha size: width × height × 1 byte
- Total: width × height × 3 bytes
```
## 常见问题
### Q: 什么时候用 RGB565什么时候用 RGB565A8
**A:**
- **RGB565**: 不需要透明效果的图片如背景、logo、纯色图标
- **RGB565A8**: 需要透明或半透明效果的图片(如 UI 元素、图标)
### Q: 什么时候需要 --swap16
**A:** 当目标硬件的字节序与生成的不匹配时使用。通常 ESP32 不需要此选项。
### Q: C 文件和二进制文件的区别?
**A:**
- **C 文件**: 直接编译到程序中,访问速度快,但增加程序大小
- **二进制文件**: 存储在外部存储(如 SPIFFS/SD卡节省程序空间但需要运行时加载
### Q: 如何查看生成的文件信息?
**A:** 运行脚本时会输出详细信息:
```
Successfully generated output.c
Format: RGB565
Image size: 64x64
Total data size: 8192 bytes
RGB565 data: 8192 bytes (4096 pixels)
Swap16: disabled
```
## 与现有代码兼容
该工具生成的文件与现有的 `gfx_img` API 完全兼容:
```c
// 两种格式使用方式完全相同
gfx_obj_t *img1 = gfx_img_create(handle);
gfx_img_set_src(img1, &rgb565_image); // RGB565 图片
gfx_obj_t *img2 = gfx_img_create(handle);
gfx_img_set_src(img2, &rgb565a8_image); // RGB565A8 图片
// 库会自动检测格式并正确渲染
```
## 性能对比
基于 ESP32-S3 测试64×64 像素图片):
| 格式 | 内存占用 | 加载时间 | 渲染帧率 |
|-----------|----------|----------|----------|
| RGB565 | 8 KB | ~5ms | ~60 FPS |
| RGB565A8 | 12 KB | ~7ms | ~45 FPS |
## 许可证
SPDX-License-Identifier: Apache-2.0
Copyright 2025 Espressif Systems (Shanghai) CO LTD

View File

@ -0,0 +1,344 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
"""
PNG to RGB565/RGB565A8 C file converter
Converts PNG images to RGB565 or RGB565A8 format with optional byte swapping
RGB565: Pure RGB565 format without alpha channel
RGB565A8: RGB565 with separate alpha channel
Supports both C file and binary output formats
Can process single files or batch process all PNG files in a directory
"""
import argparse
import os
import sys
from PIL import Image
import re
import struct
import glob
def rgb888_to_rgb565(r, g, b):
"""Convert RGB888 to RGB565"""
r = (r >> 3) & 0x1F
g = (g >> 2) & 0x3F
b = (b >> 3) & 0x1F
return (r << 11) | (g << 5) | b
def rgb565_to_bytes(rgb565, swap16=False):
"""Convert RGB565 to bytes, optionally swapping byte order"""
high_byte = (rgb565 >> 8) & 0xFF
low_byte = rgb565 & 0xFF
if swap16:
return [low_byte, high_byte]
else:
return [high_byte, low_byte]
def format_array(data, indent=4, per_line=130):
"""Format data as C array with proper indentation and line breaks"""
lines = []
for i in range(0, len(data), per_line):
line = ', '.join(f'0x{b:02x}' for b in data[i:i + per_line])
lines.append(' ' * indent + line + ',')
return '\n'.join(lines)
def generate_c_file(image_path, output_path, var_name, swap16=False, use_alpha=True):
"""Generate C file from PNG image
Args:
image_path: Input PNG file path
output_path: Output C file path
var_name: Variable name for the C array
swap16: Enable byte swapping for RGB565
use_alpha: True for RGB565A8, False for RGB565
"""
# Open and convert image
try:
img = Image.open(image_path)
if img.mode != 'RGBA':
img = img.convert('RGBA')
except Exception as e:
print(f'Error opening image {image_path}: {e}')
return False
width, height = img.size
pixels = list(img.getdata())
# Convert to RGB565 format
rgb565_data = []
alpha_data = []
for pixel in pixels:
r, g, b, a = pixel
# Convert RGB to RGB565
rgb565 = rgb888_to_rgb565(r, g, b)
# Add RGB565 bytes (2 bytes) to RGB565 array
rgb565_bytes = rgb565_to_bytes(rgb565, swap16)
rgb565_data.extend(rgb565_bytes)
# Add Alpha byte (1 byte) to Alpha array if needed
if use_alpha:
alpha_data.append(a)
# Combine data based on format
if use_alpha:
# RGB565A8: RGB565 first, then Alpha
final_data = rgb565_data + alpha_data
color_format = 'GFX_COLOR_FORMAT_RGB565A8'
format_name = 'RGB565A8'
else:
# RGB565: Only RGB565 data
final_data = rgb565_data
color_format = 'GFX_COLOR_FORMAT_RGB565'
format_name = 'RGB565'
# Generate C file content
c_content = f"""#include "gfx.h"
const uint8_t {var_name}_map[] = {{
{format_array(final_data)}
}};
const gfx_image_dsc_t {var_name} = {{
.header.cf = {color_format},
.header.magic = C_ARRAY_HEADER_MAGIC,
.header.w = {width},
.header.h = {height},
.data_size = {len(final_data)},
.data = {var_name}_map,
}};
"""
# Write to file
try:
with open(output_path, 'w') as f:
f.write(c_content)
print(f'Successfully generated {output_path}')
print(f'Format: {format_name}')
print(f'Image size: {width}x{height}')
print(f'Total data size: {len(final_data)} bytes')
print(f'RGB565 data: {len(rgb565_data)} bytes ({width * height * 2} bytes)')
if use_alpha:
print(f'Alpha data: {len(alpha_data)} bytes ({width * height} bytes)')
print(f"Swap16: {'enabled' if swap16 else 'disabled'}")
return True
except Exception as e:
print(f'Error writing file {output_path}: {e}')
return False
def generate_bin_file(image_path, output_path, swap16=False, use_alpha=True):
"""Generate binary file from PNG image with header compatible with gfx_image_header_t structure
Args:
image_path: Input PNG file path
output_path: Output binary file path
swap16: Enable byte swapping for RGB565
use_alpha: True for RGB565A8, False for RGB565
"""
# Open and convert image
try:
img = Image.open(image_path)
if img.mode != 'RGBA':
img = img.convert('RGBA')
except Exception as e:
print(f'Error opening image {image_path}: {e}')
return False
width, height = img.size
pixels = list(img.getdata())
# Convert to RGB565 format
rgb565_data = []
alpha_data = []
for pixel in pixels:
r, g, b, a = pixel
# Convert RGB to RGB565
rgb565 = rgb888_to_rgb565(r, g, b)
# Add RGB565 bytes (2 bytes) to RGB565 array
rgb565_bytes = rgb565_to_bytes(rgb565, swap16)
rgb565_data.extend(rgb565_bytes)
# Add Alpha byte (1 byte) to Alpha array if needed
if use_alpha:
alpha_data.append(a)
# Combine data based on format
if use_alpha:
# RGB565A8: RGB565 first, then Alpha
final_data = rgb565_data + alpha_data
cf = 0x0A # GFX_COLOR_FORMAT_RGB565A8
stride = width * 2 # Stride is only for RGB565 data
format_name = 'RGB565A8'
else:
# RGB565: Only RGB565 data
final_data = rgb565_data
cf = 0x04 # GFX_COLOR_FORMAT_RGB565
stride = width * 2
format_name = 'RGB565'
# Create gfx_image_header_t structure (12 bytes total)
magic = 0x19 # C_ARRAY_HEADER_MAGIC
flags = 0x0000 # No special flags
reserved = 0x0000 # Reserved field
# Pack gfx_image_header_t as bit fields in 3 uint32_t values
# First uint32: magic(8) + cf(8) + flags(16)
header_word1 = (magic & 0xFF) | ((cf & 0xFF) << 8) | ((flags & 0xFFFF) << 16)
# Second uint32: w(16) + h(16)
header_word2 = (width & 0xFFFF) | ((height & 0xFFFF) << 16)
# Third uint32: stride(16) + reserved(16)
header_word3 = (stride & 0xFFFF) | ((reserved & 0xFFFF) << 16)
# Pack header structure - use little-endian for ESP32 compatibility
# Layout: header_word1(4) + header_word2(4) + header_word3(4) = 12 bytes total
header = struct.pack('<III', header_word1, header_word2, header_word3)
# Write binary file: header (12 bytes) + image data
try:
with open(output_path, 'wb') as f:
f.write(header)
f.write(bytes(final_data))
print(f'Successfully generated {output_path}')
print(f'Format: {format_name}')
print(f'Image size: {width}x{height}')
print(f'Header size: {len(header)} bytes')
print(f'Total data size: {len(final_data)} bytes')
print(f'RGB565 data: {len(rgb565_data)} bytes ({width * height * 2} bytes)')
if use_alpha:
print(f'Alpha data: {len(alpha_data)} bytes ({width * height} bytes)')
print(f'Stride: {stride} bytes per row')
print(f'Data offset: 12 bytes')
print(f'Total file size: {len(header) + len(final_data)} bytes')
print(f"Swap16: {'enabled' if swap16 else 'disabled'}")
print(f'Header layout: magic=0x{magic:02x}, cf=0x{cf:02x}, flags=0x{flags:04x}')
return True
except Exception as e:
print(f'Error writing file {output_path}: {e}')
return False
def process_single_file(input_file, output_dir, bin_format, swap16, use_alpha):
"""Process a single PNG file"""
# Determine output path and variable name from input filename
base_name = os.path.splitext(os.path.basename(input_file))[0]
if bin_format:
# Output binary file
output_path = os.path.join(output_dir, f'{base_name}.bin')
return generate_bin_file(input_file, output_path, swap16, use_alpha)
else:
# Output C file
output_path = os.path.join(output_dir, f'{base_name}.c')
# Convert to valid C identifier
var_name = re.sub(r'[^a-zA-Z0-9_]', '_', base_name)
if var_name[0].isdigit():
var_name = 'img_' + var_name
return generate_c_file(input_file, output_path, var_name, swap16, use_alpha)
def find_png_files(input_path):
"""Find all PNG files in the given path"""
png_files = []
if os.path.isfile(input_path):
# Single file
if input_path.lower().endswith('.png'):
png_files.append(input_path)
else:
print("Warning: Input file doesn't have .png extension")
png_files.append(input_path)
elif os.path.isdir(input_path):
# Directory - find all PNG files
png_pattern = os.path.join(input_path, '*.png')
png_files = glob.glob(png_pattern)
# Also search in subdirectories
png_pattern_recursive = os.path.join(input_path, '**', '*.png')
png_files.extend(glob.glob(png_pattern_recursive, recursive=True))
# Remove duplicates and sort
png_files = sorted(list(set(png_files)))
return png_files
def main():
parser = argparse.ArgumentParser(
description='Convert PNG to RGB565 or RGB565A8 format',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Convert to RGB565A8 (with alpha) C file
%(prog)s image.png
# Convert to RGB565 (without alpha) C file
%(prog)s image.png --format rgb565
# Convert to binary format with byte swapping
%(prog)s image.png --bin --swap16
# Batch convert all PNG files in directory
%(prog)s images/ --output output/
"""
)
parser.add_argument('input', help='Input PNG file path or directory path')
parser.add_argument('--output', '-o', help='Output directory (default: current directory)')
parser.add_argument('--bin', action='store_true', help='Output binary format instead of C file')
parser.add_argument('--swap16', action='store_true', help='Enable byte swapping for RGB565')
parser.add_argument('--format', '-f', choices=['rgb565', 'rgb565a8'], default='rgb565a8',
help='Output format: rgb565 (no alpha) or rgb565a8 (with alpha, default)')
args = parser.parse_args()
# Validate input path
if not os.path.exists(args.input):
print(f"Error: Input path '{args.input}' does not exist")
return 1
# Set output directory
output_dir = args.output if args.output else '.'
if not os.path.exists(output_dir):
os.makedirs(output_dir)
# Determine if alpha channel should be included
use_alpha = (args.format == 'rgb565a8')
# Find all PNG files
png_files = find_png_files(args.input)
if not png_files:
print(f"No PNG files found in '{args.input}'")
return 1
print(f'Found {len(png_files)} PNG file(s) to process:')
for png_file in png_files:
print(f' - {png_file}')
print(f'Output format: {args.format.upper()}')
print(f'Output type: {"Binary" if args.bin else "C file"}')
print(f'Byte swap: {"Enabled" if args.swap16 else "Disabled"}')
print()
# Process each PNG file
success_count = 0
for png_file in png_files:
print(f'Processing: {png_file}')
if process_single_file(png_file, output_dir, args.bin, args.swap16, use_alpha):
success_count += 1
print() # Add blank line between files
print(f'Processing complete: {success_count}/{len(png_files)} files processed successfully')
if success_count == len(png_files):
return 0
else:
return 1
if __name__ == '__main__':
sys.exit(main())

View File

@ -0,0 +1,56 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
// Common macro definitions for renderer modules
#ifndef MAX
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#endif
#ifndef MIN
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#endif
#ifndef CONTAINER_OF
#define CONTAINER_OF(ptr, type, member) \
((type *)((char *)(ptr) - offsetof(type, member)))
#endif
/* Generic NULL-check utilities */
#ifndef GFX_IS_NULL
#define GFX_IS_NULL(p) ((p) == NULL)
#endif
#ifndef GFX_NOT_NULL
#define GFX_NOT_NULL(p) ((p) != NULL)
#endif
#ifndef GFX_RETURN_IF_NULL
#define GFX_RETURN_IF_NULL(p, retval) do { if ((p) == NULL) { return (retval); } } while (0)
#endif
#ifndef GFX_RETURN_IF_NULL_VOID
#define GFX_RETURN_IF_NULL_VOID(p) do { if ((p) == NULL) { return; } } while (0)
#endif
/* Generic object type checking macro */
#define CHECK_OBJ_TYPE(obj, expected_type, tag) \
do { \
ESP_RETURN_ON_FALSE(obj, ESP_ERR_INVALID_ARG, tag, "Object is NULL"); \
ESP_RETURN_ON_FALSE((obj)->type == (expected_type), ESP_ERR_INVALID_ARG, tag, \
"Object type mismatch (expected=%d, actual=%d)", (expected_type), (obj)->type); \
} while(0)
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,126 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#if defined(__has_include)
#if __has_include("sdkconfig.h")
#include "sdkconfig.h"
#define GFX_CONFIG_HAS_SDKCONFIG 1
#endif
#endif
#ifndef GFX_CONFIG_HAS_SDKCONFIG
#define GFX_CONFIG_HAS_SDKCONFIG 0
#endif
/*********************
* Software Blend
*********************/
#ifdef CONFIG_GFX_MESH_IMG_SCANLINE_MAX_VERTS
#define GFX_MESH_IMG_SCANLINE_MAX_VERTS CONFIG_GFX_MESH_IMG_SCANLINE_MAX_VERTS
#else
#define GFX_MESH_IMG_SCANLINE_MAX_VERTS 512U
#endif
#ifdef CONFIG_GFX_BLEND_POLYGON_MAX_INTERSECTIONS
#define GFX_BLEND_POLYGON_MAX_INTERSECTIONS CONFIG_GFX_BLEND_POLYGON_MAX_INTERSECTIONS
#else
#define GFX_BLEND_POLYGON_MAX_INTERSECTIONS 64
#endif
#ifdef CONFIG_GFX_BLEND_POLYGON_SUB_SAMPLES
#define GFX_BLEND_POLYGON_SUB_SAMPLES CONFIG_GFX_BLEND_POLYGON_SUB_SAMPLES
#else
#define GFX_BLEND_POLYGON_SUB_SAMPLES 8
#endif
#ifdef CONFIG_GFX_BLEND_POLYGON_COVERAGE_MAX_WIDTH
#define GFX_BLEND_POLYGON_COVERAGE_MAX_WIDTH CONFIG_GFX_BLEND_POLYGON_COVERAGE_MAX_WIDTH
#else
#define GFX_BLEND_POLYGON_COVERAGE_MAX_WIDTH 512
#endif
#ifdef CONFIG_GFX_BLEND_POLYGON_INWARD_AA
#define GFX_BLEND_POLYGON_INWARD_AA 1
#elif GFX_CONFIG_HAS_SDKCONFIG
#define GFX_BLEND_POLYGON_INWARD_AA 0
#else
#define GFX_BLEND_POLYGON_INWARD_AA 1
#endif
#ifdef CONFIG_GFX_BLEND_POLYGON_SOLID_HARD_EDGE
#define GFX_BLEND_POLYGON_SOLID_HARD_EDGE 1
#elif GFX_CONFIG_HAS_SDKCONFIG
#define GFX_BLEND_POLYGON_SOLID_HARD_EDGE 0
#else
#define GFX_BLEND_POLYGON_SOLID_HARD_EDGE 1
#endif
/*********************
* Motion Widget
*********************/
#define GFX_MOTION_DEFAULT_TIMER_PERIOD_MS 33U
#define GFX_MOTION_DEFAULT_DAMPING_DIV 4
#define GFX_MOTION_DEFAULT_STROKE_COLOR 0x1F1F1F
#define GFX_MOTION_DEFAULT_SEG_OPACITY 0xFFU
#define GFX_MOTION_RING_SEGS_MIN 16U
#define GFX_MOTION_RING_SEGS_MAX 48U
#ifdef CONFIG_GFX_MOTION_BEZIER_STROKE_SEGS_PER_SEG
#define GFX_MOTION_BEZIER_STROKE_SEGS_PER_SEG CONFIG_GFX_MOTION_BEZIER_STROKE_SEGS_PER_SEG
#else
#define GFX_MOTION_BEZIER_STROKE_SEGS_PER_SEG 6
#endif
#ifdef CONFIG_GFX_MOTION_BEZIER_FILL_LOOP_SEGS_PER_SEG
#define GFX_MOTION_BEZIER_FILL_LOOP_SEGS_PER_SEG CONFIG_GFX_MOTION_BEZIER_FILL_LOOP_SEGS_PER_SEG
#else
#define GFX_MOTION_BEZIER_FILL_LOOP_SEGS_PER_SEG 12
#endif
#ifdef CONFIG_GFX_MOTION_BEZIER_FILL_SEGS
#define GFX_MOTION_BEZIER_FILL_SEGS CONFIG_GFX_MOTION_BEZIER_FILL_SEGS
#else
#define GFX_MOTION_BEZIER_FILL_SEGS 24
#endif
#ifdef CONFIG_GFX_MOTION_HUB_FILL_MAX_POINTS
#define GFX_MOTION_HUB_FILL_MAX_POINTS CONFIG_GFX_MOTION_HUB_FILL_MAX_POINTS
#else
#define GFX_MOTION_HUB_FILL_MAX_POINTS 512
#endif
#ifdef CONFIG_GFX_MOTION_BEZIER_FILL_RASTERIZER_TRIANGLE
#define GFX_MOTION_BEZIER_FILL_USE_SCANLINE 0
#else
#define GFX_MOTION_BEZIER_FILL_USE_SCANLINE 1
#endif
/*********************
* Label Widget
*********************/
#ifdef CONFIG_GFX_LABEL_GLYPH_CACHE_MAX_ENTRIES
#define GFX_LABEL_GLYPH_CACHE_MAX_ENTRIES CONFIG_GFX_LABEL_GLYPH_CACHE_MAX_ENTRIES
#else
#define GFX_LABEL_GLYPH_CACHE_MAX_ENTRIES 64
#endif
#ifdef CONFIG_GFX_LABEL_GLYPH_CACHE_MAX_BITMAP_BYTES
#define GFX_LABEL_GLYPH_CACHE_MAX_BITMAP_BYTES CONFIG_GFX_LABEL_GLYPH_CACHE_MAX_BITMAP_BYTES
#else
#define GFX_LABEL_GLYPH_CACHE_MAX_BITMAP_BYTES (12 * 1024)
#endif
#ifdef CONFIG_GFX_LABEL_GLYPH_ATLAS_PAGE_BYTES
#define GFX_LABEL_GLYPH_ATLAS_PAGE_BYTES CONFIG_GFX_LABEL_GLYPH_ATLAS_PAGE_BYTES
#else
#define GFX_LABEL_GLYPH_ATLAS_PAGE_BYTES 1024
#endif

View File

@ -0,0 +1,26 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "core/gfx_log.h"
#ifndef GFX_LOG_MODULE
#error "GFX_LOG_MODULE must be defined before including common/gfx_log_priv.h"
#endif
#define GFX_LOG_WRITE(level, tag, format, ...) \
do { \
if (gfx_log_should_output(GFX_LOG_MODULE, level)) { \
gfx_log_write(GFX_LOG_MODULE, level, tag, format, ##__VA_ARGS__); \
} \
} while (0)
#define GFX_LOGE(tag, format, ...) GFX_LOG_WRITE(GFX_LOG_LEVEL_ERROR, tag, format, ##__VA_ARGS__)
#define GFX_LOGW(tag, format, ...) GFX_LOG_WRITE(GFX_LOG_LEVEL_WARN, tag, format, ##__VA_ARGS__)
#define GFX_LOGI(tag, format, ...) GFX_LOG_WRITE(GFX_LOG_LEVEL_INFO, tag, format, ##__VA_ARGS__)
#define GFX_LOGD(tag, format, ...) GFX_LOG_WRITE(GFX_LOG_LEVEL_DEBUG, tag, format, ##__VA_ARGS__)
#define GFX_LOGV(tag, format, ...) GFX_LOG_WRITE(GFX_LOG_LEVEL_VERBOSE, tag, format, ##__VA_ARGS__)

View File

@ -0,0 +1,30 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "core/gfx_types.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
gfx_color_t gfx_color_hex(uint32_t c)
{
gfx_color_t r;
r.full = (uint16_t)(((c & 0xF80000) >> 8) | ((c & 0xFC00) >> 5) | ((c & 0xFF) >> 3));
return r;
}

View File

@ -0,0 +1,201 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*********************
* INCLUDES
*********************/
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include "esp_timer.h"
#include "core/gfx_log.h"
/**********************
* STATIC VARIABLES
**********************/
static const char *s_module_names[GFX_LOG_MODULE_COUNT] = {
[GFX_LOG_MODULE_CORE] = "core",
[GFX_LOG_MODULE_DISP] = "disp",
[GFX_LOG_MODULE_OBJ] = "obj",
[GFX_LOG_MODULE_REFR] = "refr",
[GFX_LOG_MODULE_RENDER] = "render",
[GFX_LOG_MODULE_TIMER] = "timer",
[GFX_LOG_MODULE_TOUCH] = "touch",
[GFX_LOG_MODULE_IMG_DEC] = "img_dec",
[GFX_LOG_MODULE_LABEL] = "label",
[GFX_LOG_MODULE_LABEL_OBJ] = "label_obj",
[GFX_LOG_MODULE_DRAW_LABEL] = "draw_label",
[GFX_LOG_MODULE_FONT_LV] = "font_lv",
[GFX_LOG_MODULE_FONT_FT] = "font_ft",
[GFX_LOG_MODULE_IMG] = "img",
[GFX_LOG_MODULE_QRCODE] = "qrcode",
[GFX_LOG_MODULE_BUTTON] = "button",
[GFX_LOG_MODULE_ANIM] = "anim",
[GFX_LOG_MODULE_ANIM_DEC] = "anim_dec",
[GFX_LOG_MODULE_MOTION] = "motion",
[GFX_LOG_MODULE_EAF_DEC] = "eaf_dec",
[GFX_LOG_MODULE_QRCODE_LIB] = "qrcode_lib",
};
static gfx_log_level_t s_module_levels[GFX_LOG_MODULE_COUNT];
static bool s_log_levels_initialized;
/**********************
* STATIC FUNCTIONS
**********************/
#define GFX_LOG_COLOR_RED "\033[0;31m"
#define GFX_LOG_COLOR_YELLOW "\033[0;33m"
#define GFX_LOG_COLOR_GREEN "\033[0;32m"
#define GFX_LOG_COLOR_CYAN "\033[0;36m"
#define GFX_LOG_COLOR_WHITE "\033[0;37m"
#define GFX_LOG_COLOR_RESET "\033[0m"
static char gfx_log_level_to_char(gfx_log_level_t level)
{
switch (level) {
case GFX_LOG_LEVEL_ERROR:
return 'E';
case GFX_LOG_LEVEL_WARN:
return 'W';
case GFX_LOG_LEVEL_INFO:
return 'I';
case GFX_LOG_LEVEL_DEBUG:
return 'D';
case GFX_LOG_LEVEL_VERBOSE:
return 'V';
case GFX_LOG_LEVEL_NONE:
default:
return 'N';
}
}
static const char *gfx_log_level_to_color(gfx_log_level_t level)
{
switch (level) {
case GFX_LOG_LEVEL_ERROR:
return GFX_LOG_COLOR_RED;
case GFX_LOG_LEVEL_WARN:
return GFX_LOG_COLOR_YELLOW;
case GFX_LOG_LEVEL_INFO:
return GFX_LOG_COLOR_GREEN;
case GFX_LOG_LEVEL_DEBUG:
return GFX_LOG_COLOR_CYAN;
case GFX_LOG_LEVEL_VERBOSE:
return GFX_LOG_COLOR_WHITE;
case GFX_LOG_LEVEL_NONE:
default:
return GFX_LOG_COLOR_RESET;
}
}
static void gfx_log_init_levels(void)
{
if (s_log_levels_initialized) {
return;
}
for (int i = 0; i < GFX_LOG_MODULE_COUNT; i++) {
s_module_levels[i] = GFX_LOG_LEVEL_INFO;
}
s_log_levels_initialized = true;
}
/**********************
* PUBLIC FUNCTIONS
**********************/
void gfx_log_set_level(gfx_log_module_t module, gfx_log_level_t level)
{
gfx_log_init_levels();
if (module < 0 || module >= GFX_LOG_MODULE_COUNT) {
return;
}
s_module_levels[module] = level;
}
gfx_log_level_t gfx_log_get_level(gfx_log_module_t module)
{
gfx_log_init_levels();
if (module < 0 || module >= GFX_LOG_MODULE_COUNT) {
return GFX_LOG_LEVEL_NONE;
}
return s_module_levels[module];
}
void gfx_log_set_level_all(gfx_log_level_t level)
{
gfx_log_init_levels();
for (int i = 0; i < GFX_LOG_MODULE_COUNT; i++) {
s_module_levels[i] = level;
}
}
bool gfx_log_should_output(gfx_log_module_t module, gfx_log_level_t level)
{
gfx_log_init_levels();
if (module < 0 || module >= GFX_LOG_MODULE_COUNT) {
return false;
}
if (level == GFX_LOG_LEVEL_NONE) {
return false;
}
return level <= s_module_levels[module];
}
const char *gfx_log_module_name(gfx_log_module_t module)
{
if (module < 0 || module >= GFX_LOG_MODULE_COUNT) {
return "unknown";
}
return s_module_names[module];
}
void gfx_log_writev(gfx_log_module_t module, gfx_log_level_t level, const char *tag, const char *format, va_list args)
{
const char *module_name;
const char *color;
int64_t ts_us;
if (!gfx_log_should_output(module, level)) {
return;
}
module_name = gfx_log_module_name(module);
color = gfx_log_level_to_color(level);
ts_us = esp_timer_get_time();
if (tag != NULL && tag[0] != '\0' && strcmp(tag, module_name) != 0) {
printf("%s%c (%" PRIi64 ") %s/%s: ", color, gfx_log_level_to_char(level), ts_us / 1000, module_name, tag);
} else {
printf("%s%c (%" PRIi64 ") %s: ", color, gfx_log_level_to_char(level), ts_us / 1000, module_name);
}
vprintf(format, args);
printf("%s\n", GFX_LOG_COLOR_RESET);
}
void gfx_log_write(gfx_log_module_t module, gfx_log_level_t level, const char *tag, const char *format, ...)
{
va_list args;
va_start(args, format);
gfx_log_writev(module, level, tag, format, args);
va_end(args);
}

View File

@ -0,0 +1,436 @@
/*
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*********************
* INCLUDES
*********************/
#include <string.h>
#define GFX_LOG_MODULE GFX_LOG_MODULE_DISP
#include "common/gfx_log_priv.h"
#include "esp_heap_caps.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "soc/soc_caps.h"
#include "core/display/gfx_disp_priv.h"
#include "core/display/gfx_refr_priv.h"
#include "core/runtime/gfx_core_priv.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC VARIABLES
**********************/
static const char *TAG = "disp";
/**********************
* STATIC PROTOTYPES
**********************/
static void gfx_disp_init_default_state(gfx_disp_t *disp);
/**********************
* STATIC FUNCTIONS
**********************/
static void gfx_disp_init_default_state(gfx_disp_t *disp)
{
disp->child_list = NULL;
disp->next = NULL;
disp->buf.buf_act = disp->buf.buf1;
disp->style.bg_color.full = 0x0000;
disp->style.bg_enable = true;
}
/**********************
* PUBLIC FUNCTIONS
**********************/
esp_err_t gfx_disp_buf_free(gfx_disp_t *disp)
{
if (!disp) {
return ESP_OK;
}
if (!disp->buf.ext_bufs) {
if (disp->buf.buf1) {
heap_caps_free(disp->buf.buf1);
disp->buf.buf1 = NULL;
}
if (disp->buf.buf2) {
heap_caps_free(disp->buf.buf2);
disp->buf.buf2 = NULL;
}
}
disp->buf.buf_pixels = 0;
disp->buf.ext_bufs = false;
return ESP_OK;
}
esp_err_t gfx_disp_buf_init(gfx_disp_t *disp, const gfx_disp_config_t *cfg)
{
if (cfg->buffers.buf1 != NULL) {
disp->buf.buf1 = (uint16_t *)cfg->buffers.buf1;
disp->buf.buf2 = (uint16_t *)cfg->buffers.buf2;
if (cfg->buffers.buf_pixels > 0) {
disp->buf.buf_pixels = cfg->buffers.buf_pixels;
} else {
GFX_LOGW(TAG, "init display buffers: buf_pixels is zero, using screen size");
disp->buf.buf_pixels = disp->res.h_res * disp->res.v_res;
}
disp->buf.ext_bufs = true;
} else {
#if SOC_PSRAM_DMA_CAPABLE == 0
if (cfg->flags.buff_dma && cfg->flags.buff_spiram) {
GFX_LOGW(TAG, "init display buffers: dma with spiram is not supported");
return ESP_ERR_NOT_SUPPORTED;
}
#endif
uint32_t buff_caps = 0;
if (cfg->flags.buff_dma) {
buff_caps |= MALLOC_CAP_DMA;
}
if (cfg->flags.buff_spiram) {
buff_caps |= MALLOC_CAP_SPIRAM;
}
if (buff_caps == 0) {
buff_caps = MALLOC_CAP_DEFAULT;
}
size_t buf_pixels = cfg->buffers.buf_pixels > 0 ? cfg->buffers.buf_pixels : disp->res.h_res * disp->res.v_res;
disp->buf.buf1 = (uint16_t *)heap_caps_malloc(buf_pixels * sizeof(uint16_t), buff_caps);
if (!disp->buf.buf1) {
GFX_LOGE(TAG, "init display buffers: allocate frame buffer 1 failed");
return ESP_ERR_NO_MEM;
}
if (cfg->flags.double_buffer) {
disp->buf.buf2 = (uint16_t *)heap_caps_malloc(buf_pixels * sizeof(uint16_t), buff_caps);
if (!disp->buf.buf2) {
GFX_LOGE(TAG, "init display buffers: allocate frame buffer 2 failed");
heap_caps_free(disp->buf.buf1);
disp->buf.buf1 = NULL;
return ESP_ERR_NO_MEM;
}
} else {
disp->buf.buf2 = NULL;
}
disp->buf.buf_pixels = buf_pixels;
disp->buf.ext_bufs = false;
}
disp->buf.buf_act = disp->buf.buf1;
disp->style.bg_color.full = 0x0000;
return ESP_OK;
}
void gfx_disp_del(gfx_disp_t *disp)
{
if (!disp) {
return;
}
gfx_core_context_t *ctx = (gfx_core_context_t *)disp->ctx;
if (ctx != NULL) {
if (ctx->disp == disp) {
ctx->disp = disp->next;
} else {
gfx_disp_t *prev = ctx->disp;
while (prev != NULL && prev->next != disp) {
prev = prev->next;
}
if (prev != NULL) {
prev->next = disp->next;
}
}
}
gfx_obj_child_t *child_node = disp->child_list;
while (child_node != NULL) {
gfx_obj_child_t *next_child = child_node->next;
free(child_node);
child_node = next_child;
}
disp->child_list = NULL;
if (disp->sync.event_group) {
vEventGroupDelete(disp->sync.event_group);
disp->sync.event_group = NULL;
}
gfx_disp_buf_free(disp);
disp->ctx = NULL;
disp->next = NULL;
}
gfx_disp_t *gfx_disp_add(gfx_handle_t handle, const gfx_disp_config_t *cfg)
{
esp_err_t ret;
gfx_core_context_t *ctx = (gfx_core_context_t *)handle;
if (ctx == NULL || cfg == NULL) {
GFX_LOGE(TAG, "create display: handle or config is NULL");
return NULL;
}
gfx_disp_t *new_disp = (gfx_disp_t *)malloc(sizeof(gfx_disp_t));
if (new_disp == NULL) {
GFX_LOGE(TAG, "create display: allocate display state failed");
return NULL;
}
memset(new_disp, 0, sizeof(gfx_disp_t));
new_disp->ctx = ctx;
new_disp->res.h_res = cfg->h_res;
new_disp->res.v_res = cfg->v_res;
new_disp->flags.swap = cfg->flags.swap;
new_disp->flags.full_frame = cfg->flags.full_frame;
new_disp->cb.flush_cb = cfg->flush_cb;
new_disp->cb.update_cb = cfg->update_cb;
new_disp->cb.user_data = cfg->user_data;
gfx_disp_init_default_state(new_disp);
if (cfg->flags.full_frame && cfg->buffers.buf_pixels > 0) {
uint32_t screen_px = new_disp->res.h_res * new_disp->res.v_res;
if (cfg->buffers.buf_pixels != screen_px) {
GFX_LOGE(TAG, "create display: full_frame requires buf_pixels (%u) == screen size (%u)",
(unsigned)cfg->buffers.buf_pixels, (unsigned)screen_px);
free(new_disp);
return NULL;
}
}
new_disp->sync.event_group = xEventGroupCreate();
if (new_disp->sync.event_group == NULL) {
GFX_LOGE(TAG, "create display: create event group failed");
free(new_disp);
return NULL;
}
if (cfg->buffers.buf1 != NULL) {
new_disp->buf.buf1 = (uint16_t *)cfg->buffers.buf1;
new_disp->buf.buf2 = (uint16_t *)cfg->buffers.buf2;
new_disp->buf.buf_pixels = cfg->buffers.buf_pixels > 0 ? cfg->buffers.buf_pixels : new_disp->res.h_res * new_disp->res.v_res;
new_disp->buf.ext_bufs = true;
new_disp->buf.buf_act = new_disp->buf.buf1;
} else {
ret = gfx_disp_buf_init(new_disp, cfg);
if (ret != ESP_OK) {
vEventGroupDelete(new_disp->sync.event_group);
free(new_disp);
return NULL;
}
}
if (ctx->disp == NULL) {
ctx->disp = new_disp;
} else {
gfx_disp_t *tail = ctx->disp;
while (tail->next != NULL) {
tail = tail->next;
}
tail->next = new_disp;
}
gfx_disp_refresh_all(new_disp);
GFX_LOGD(TAG, "create display: object created");
return new_disp;
}
esp_err_t gfx_disp_add_child(gfx_disp_t *disp, void *src)
{
if (disp == NULL || src == NULL) {
GFX_LOGE(TAG, "add display child: display or source is NULL");
return ESP_ERR_INVALID_ARG;
}
gfx_core_context_t *ctx = disp->ctx;
if (ctx == NULL) {
return ESP_ERR_INVALID_STATE;
}
((gfx_obj_t *)src)->disp = disp;
gfx_obj_child_t *new_child = (gfx_obj_child_t *)malloc(sizeof(gfx_obj_child_t));
if (new_child == NULL) {
GFX_LOGE(TAG, "add display child: allocate child node failed");
return ESP_ERR_NO_MEM;
}
new_child->src = src;
new_child->next = NULL;
if (disp->child_list == NULL) {
disp->child_list = new_child;
} else {
gfx_obj_child_t *current = disp->child_list;
while (current->next != NULL) {
current = current->next;
}
current->next = new_child;
}
return ESP_OK;
}
esp_err_t gfx_disp_remove_child(gfx_disp_t *disp, void *src)
{
if (disp == NULL || src == NULL) {
GFX_LOGE(TAG, "remove display child: display or source is NULL");
return ESP_ERR_INVALID_ARG;
}
gfx_obj_child_t *current = disp->child_list;
gfx_obj_child_t *prev = NULL;
while (current != NULL) {
if (current->src == src) {
if (prev == NULL) {
disp->child_list = current->next;
} else {
prev->next = current->next;
}
free(current);
return ESP_OK;
}
prev = current;
current = current->next;
}
return ESP_ERR_NOT_FOUND;
}
esp_err_t gfx_disp_delete_children(gfx_disp_t *disp)
{
if (disp == NULL) {
return ESP_ERR_INVALID_ARG;
}
while (disp->child_list != NULL) {
gfx_obj_t *obj = (gfx_obj_t *)disp->child_list->src;
if (obj == NULL) {
gfx_obj_child_t *node = disp->child_list;
disp->child_list = node->next;
free(node);
continue;
}
esp_err_t ret = gfx_obj_delete(obj);
if (ret != ESP_OK) {
return ret;
}
}
return ESP_OK;
}
/**********************
* REFRESH AND FLUSH
**********************/
void gfx_disp_refresh_all(gfx_disp_t *disp)
{
if (disp == NULL) {
GFX_LOGE(TAG, "refresh display: display is NULL");
return;
}
gfx_area_t full_screen;
full_screen.x1 = 0;
full_screen.y1 = 0;
full_screen.x2 = (int)disp->res.h_res - 1;
full_screen.y2 = (int)disp->res.v_res - 1;
gfx_invalidate_area_disp(disp, &full_screen);
}
bool gfx_disp_flush_ready(gfx_disp_t *disp, bool swap_act_buf)
{
if (disp == NULL || disp->sync.event_group == NULL) {
return false;
}
disp->render.swap_act_buf = swap_act_buf;
if (xPortInIsrContext()) {
BaseType_t pxHigherPriorityTaskWoken = pdFALSE;
bool result = xEventGroupSetBitsFromISR(disp->sync.event_group, WAIT_FLUSH_DONE, &pxHigherPriorityTaskWoken);
if (pxHigherPriorityTaskWoken == pdTRUE) {
portYIELD_FROM_ISR();
}
return result;
}
return xEventGroupSetBits(disp->sync.event_group, WAIT_FLUSH_DONE);
}
/**********************
* CONFIG AND STATUS
**********************/
void *gfx_disp_get_user_data(gfx_disp_t *disp)
{
if (disp == NULL) {
GFX_LOGE(TAG, "get display user data: display is NULL");
return NULL;
}
return disp->cb.user_data;
}
uint32_t gfx_disp_get_hor_res(gfx_disp_t *disp)
{
if (disp == NULL) {
return DEFAULT_SCREEN_WIDTH;
}
return disp->res.h_res;
}
uint32_t gfx_disp_get_ver_res(gfx_disp_t *disp)
{
if (disp == NULL) {
return DEFAULT_SCREEN_HEIGHT;
}
return disp->res.v_res;
}
esp_err_t gfx_disp_set_bg_color(gfx_disp_t *disp, gfx_color_t color)
{
if (disp == NULL) {
GFX_LOGE(TAG, "set display background color: display is NULL");
return ESP_ERR_INVALID_ARG;
}
disp->style.bg_color.full = color.full;
GFX_LOGD(TAG, "set display background color: 0x%04X", color.full);
return ESP_OK;
}
esp_err_t gfx_disp_set_bg_enable(gfx_disp_t *disp, bool enable)
{
if (disp == NULL) {
GFX_LOGE(TAG, "set display background enable: display is NULL");
return ESP_ERR_INVALID_ARG;
}
disp->style.bg_enable = enable;
return ESP_OK;
}
bool gfx_disp_is_flushing_last(gfx_disp_t *disp)
{
if (disp == NULL) {
return false;
}
return disp->render.flushing_last;
}
esp_err_t gfx_disp_get_perf_stats(gfx_disp_t *disp, gfx_disp_perf_stats_t *out_stats)
{
if (disp == NULL || out_stats == NULL) {
return ESP_ERR_INVALID_ARG;
}
out_stats->dirty_pixels = disp->render.dirty_pixels;
out_stats->frame_time_us = disp->render.frame_time_us;
out_stats->render_time_us = disp->render.render_time_us;
out_stats->flush_time_us = disp->render.flush_time_us;
out_stats->flush_count = disp->render.flush_count;
out_stats->blend = disp->render.blend;
return ESP_OK;
}

View File

@ -0,0 +1,162 @@
/*
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "core/gfx_disp.h"
#include "core/object/gfx_obj_priv.h"
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* TYPEDEFS
*********************/
struct gfx_core_context;
/*********************
* DEFINES
*********************/
#ifdef CONFIG_GFX_DISP_INV_BUF_SIZE
#define GFX_DISP_INV_BUF_SIZE CONFIG_GFX_DISP_INV_BUF_SIZE
#else
#define GFX_DISP_INV_BUF_SIZE 64
#endif
/*********************
* INTERNAL STRUCTS
*********************/
/** Per-display state; one per screen, linked list for multi-display. Fields grouped by category. */
struct gfx_disp {
struct gfx_disp *next;
struct gfx_core_context *ctx;
/** Resolution */
struct {
uint32_t h_res;
uint32_t v_res;
} res;
/** Option flags */
struct {
unsigned char swap : 1;
unsigned char full_frame : 1;
} flags;
/** Callbacks and user data */
struct {
gfx_disp_flush_cb_t flush_cb;
gfx_disp_update_cb_t update_cb;
void *user_data;
} cb;
/** Sync (event group for flush done) */
struct {
EventGroupHandle_t event_group;
} sync;
/** Child object list */
gfx_obj_child_t *child_list;
/** Frame buffers */
struct {
uint16_t *buf1;
uint16_t *buf2;
uint16_t *buf_act;
size_t buf_pixels;
bool ext_bufs;
} buf;
/** Display style (e.g. background color) */
struct {
gfx_color_t bg_color;
bool bg_enable; /**< true = fill background before draw; default true */
} style;
/** Render state (flush / swap) */
struct {
bool flushing_last;
bool swap_act_buf;
uint32_t dirty_pixels;
uint64_t frame_time_us;
uint64_t render_time_us;
uint64_t flush_time_us;
uint32_t flush_count;
gfx_blend_perf_stats_t blend;
} render;
/** Dirty / invalidation state */
struct {
gfx_area_t areas[GFX_DISP_INV_BUF_SIZE];
uint8_t merged[GFX_DISP_INV_BUF_SIZE];
uint8_t count;
} dirty;
/** Pending sync: dirty areas from previous frame to sync into buf_act at next render start (only non-merged areas, no merged flags) */
struct {
gfx_area_t areas[GFX_DISP_INV_BUF_SIZE];
uint8_t count;
} sync_pending;
};
/*********************
* INTERNAL API
*********************/
/* Buffer helpers (used by gfx_disp.c and gfx_core.c deinit) */
/**
* @brief Free display frame buffers
* @param disp Display whose buffers to free (internal alloc only; ext_bufs are not freed)
* @return ESP_OK
* @internal Used by gfx_core deinit when tearing down displays.
*/
esp_err_t gfx_disp_buf_free(gfx_disp_t *disp);
/**
* @brief Initialize display buffers from config
* @param disp Display to init (h_res, v_res already set)
* @param cfg Display config (buffers.buf1/buf2/buf_pixels)
* @return ESP_OK on success, ESP_ERR_NO_MEM if internal alloc fails
* @internal Used by gfx_disp_add when cfg->buffers.buf1 is NULL.
*/
esp_err_t gfx_disp_buf_init(gfx_disp_t *disp, const gfx_disp_config_t *cfg);
/* Object/render helpers (obj/widget/render only, not in public gfx_disp.h) */
/**
* @brief Add a child object to a display
* @param disp Display to attach to
* @param type Child type (GFX_OBJ_TYPE_IMAGE, GFX_OBJ_TYPE_LABEL, etc.)
* @param src Child object pointer (e.g. gfx_obj_t *)
* @return ESP_OK on success
* @internal Used by gfx_anim_create, gfx_img_create, gfx_label_create, gfx_qrcode_create.
*/
esp_err_t gfx_disp_add_child(gfx_disp_t *disp, void *src);
/**
* @brief Remove a child object from a display
* @param disp Display that owns the child
* @param src Child object pointer to remove (e.g. gfx_obj_t *)
* @return ESP_OK on success, ESP_ERR_NOT_FOUND if not in list
* @internal Used by gfx_obj_delete.
*/
esp_err_t gfx_disp_remove_child(gfx_disp_t *disp, void *src);
/**
* @brief Delete and detach every child object owned by a display.
* @param disp Display that owns the child list
* @return ESP_OK on success
* @internal Used during display/core teardown to ensure widget destructors run.
*/
esp_err_t gfx_disp_delete_children(gfx_disp_t *disp);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,300 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*********************
* INCLUDES
*********************/
#include <string.h>
#include <inttypes.h>
#define GFX_LOG_MODULE GFX_LOG_MODULE_REFR
#include "common/gfx_log_priv.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "core/display/gfx_refr_priv.h"
#include "core/runtime/gfx_core_priv.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC VARIABLES
**********************/
static const char *TAG = "refr";
/**********************
* STATIC PROTOTYPES
**********************/
/**********************
* STATIC FUNCTIONS
**********************/
/**********************
* PUBLIC FUNCTIONS
**********************/
/* Area helpers */
void gfx_area_copy(gfx_area_t *dest, const gfx_area_t *src)
{
dest->x1 = src->x1;
dest->y1 = src->y1;
dest->x2 = src->x2;
dest->y2 = src->y2;
}
bool gfx_area_is_in(const gfx_area_t *area_in, const gfx_area_t *area_parent)
{
if (area_in->x1 >= area_parent->x1 &&
area_in->y1 >= area_parent->y1 &&
area_in->x2 <= area_parent->x2 &&
area_in->y2 <= area_parent->y2) {
return true;
}
return false;
}
bool gfx_area_intersect(gfx_area_t *result, const gfx_area_t *a1, const gfx_area_t *a2)
{
gfx_coord_t x1 = (a1->x1 > a2->x1) ? a1->x1 : a2->x1;
gfx_coord_t y1 = (a1->y1 > a2->y1) ? a1->y1 : a2->y1;
gfx_coord_t x2 = (a1->x2 < a2->x2) ? a1->x2 : a2->x2;
gfx_coord_t y2 = (a1->y2 < a2->y2) ? a1->y2 : a2->y2;
if (x1 <= x2 && y1 <= y2) {
result->x1 = x1;
result->y1 = y1;
result->x2 = x2;
result->y2 = y2;
return true;
}
return false;
}
bool gfx_area_intersect_exclusive(gfx_area_t *result, const gfx_area_t *a1, const gfx_area_t *a2)
{
gfx_coord_t x1 = (a1->x1 > a2->x1) ? a1->x1 : a2->x1;
gfx_coord_t y1 = (a1->y1 > a2->y1) ? a1->y1 : a2->y1;
gfx_coord_t x2 = (a1->x2 < a2->x2) ? a1->x2 : a2->x2;
gfx_coord_t y2 = (a1->y2 < a2->y2) ? a1->y2 : a2->y2;
if (x1 < x2 && y1 < y2) {
result->x1 = x1;
result->y1 = y1;
result->x2 = x2;
result->y2 = y2;
return true;
}
return false;
}
uint32_t gfx_area_get_size(const gfx_area_t *area)
{
uint32_t width = area->x2 - area->x1 + 1;
uint32_t height = area->y2 - area->y1 + 1;
return width * height;
}
bool gfx_area_is_on(const gfx_area_t *a1, const gfx_area_t *a2)
{
/* Check if areas are completely separate */
if ((a1->x1 > a2->x2) ||
(a2->x1 > a1->x2) ||
(a1->y1 > a2->y2) ||
(a2->y1 > a1->y2)) {
return false;
}
return true;
}
void gfx_area_join(gfx_area_t *result, const gfx_area_t *a1, const gfx_area_t *a2)
{
result->x1 = (a1->x1 < a2->x1) ? a1->x1 : a2->x1;
result->y1 = (a1->y1 < a2->y1) ? a1->y1 : a2->y1;
result->x2 = (a1->x2 > a2->x2) ? a1->x2 : a2->x2;
result->y2 = (a1->y2 > a2->y2) ? a1->y2 : a2->y2;
}
void gfx_refr_merge_areas(gfx_disp_t *disp)
{
uint32_t src_idx;
uint32_t dst_idx;
gfx_area_t merged_area;
if (disp == NULL) {
return;
}
memset(disp->dirty.merged, 0, sizeof(disp->dirty.merged));
for (dst_idx = 0; dst_idx < disp->dirty.count; dst_idx++) {
if (disp->dirty.merged[dst_idx] != 0) {
continue;
}
for (src_idx = 0; src_idx < disp->dirty.count; src_idx++) {
if (disp->dirty.merged[src_idx] != 0 || dst_idx == src_idx) {
continue;
}
if (!gfx_area_is_on(&disp->dirty.areas[dst_idx], &disp->dirty.areas[src_idx])) {
continue;
}
gfx_area_join(&merged_area, &disp->dirty.areas[dst_idx], &disp->dirty.areas[src_idx]);
uint32_t merged_size = gfx_area_get_size(&merged_area);
uint32_t separate_size = gfx_area_get_size(&disp->dirty.areas[dst_idx]) +
gfx_area_get_size(&disp->dirty.areas[src_idx]);
if (merged_size < separate_size) {
gfx_area_copy(&disp->dirty.areas[dst_idx], &merged_area);
disp->dirty.merged[src_idx] = 1;
GFX_LOGD(TAG, "merge dirty areas: [%" PRIu32 "] into [%" PRIu32 "], saved %" PRIu32 " pixels",
src_idx, dst_idx, separate_size - merged_size);
}
}
}
}
void gfx_invalidate_area_disp(gfx_disp_t *disp, const gfx_area_t *area_p)
{
if (disp == NULL) {
return;
}
if (area_p == NULL) {
disp->dirty.count = 0;
memset(disp->dirty.merged, 0, sizeof(disp->dirty.merged));
GFX_LOGD(TAG, "invalidate area: cleared all dirty areas");
return;
}
gfx_area_t screen_area;
screen_area.x1 = 0;
screen_area.y1 = 0;
screen_area.x2 = disp->res.h_res - 1;
screen_area.y2 = disp->res.v_res - 1;
gfx_area_t clipped_area;
bool success = gfx_area_intersect(&clipped_area, area_p, &screen_area);
if (!success) {
GFX_LOGD(TAG, "invalidate area: area is out of screen bounds");
return;
}
for (uint8_t i = 0; i < disp->dirty.count; i++) {
if (gfx_area_is_in(&clipped_area, &disp->dirty.areas[i])) {
GFX_LOGD(TAG, "invalidate area: area is already covered by dirty area %d", i);
return;
}
}
if (disp->dirty.count < GFX_DISP_INV_BUF_SIZE) {
gfx_area_copy(&disp->dirty.areas[disp->dirty.count], &clipped_area);
disp->dirty.count++;
GFX_LOGD(TAG, "invalidate area: added [%d,%d,%d,%d], total=%d",
clipped_area.x1, clipped_area.y1, clipped_area.x2, clipped_area.y2, disp->dirty.count);
} else {
GFX_LOGW(TAG, "invalidate area: dirty buffer is full[%d], marking full screen", disp->dirty.count);
disp->dirty.count = 1;
gfx_area_copy(&disp->dirty.areas[0], &screen_area);
}
/* Wake render task so it refreshes without waiting for the next timer tick */
gfx_core_context_t *ctx = (gfx_core_context_t *)disp->ctx;
if (ctx != NULL && ctx->sync.render_events != NULL) {
xEventGroupSetBits(ctx->sync.render_events, GFX_EVENT_INVALIDATE);
}
}
void gfx_invalidate_area(gfx_handle_t handle, const gfx_area_t *area_p)
{
if (handle == NULL) {
GFX_LOGE(TAG, "invalidate area: handle is NULL");
return;
}
gfx_core_context_t *ctx = (gfx_core_context_t *)handle;
if (area_p == NULL) {
for (gfx_disp_t *d = ctx->disp; d != NULL; d = d->next) {
gfx_invalidate_area_disp(d, NULL);
}
return;
}
/* Invalidate first display (backward compat) */
if (ctx->disp != NULL) {
gfx_invalidate_area_disp(ctx->disp, area_p);
}
}
void gfx_obj_invalidate(gfx_obj_t *obj)
{
if (obj == NULL) {
GFX_LOGE(TAG, "invalidate object: object is NULL");
return;
}
if (obj->disp == NULL) {
GFX_LOGE(TAG, "invalidate object: object has no display");
return;
}
gfx_area_t obj_area;
obj_area.x1 = obj->geometry.x;
obj_area.y1 = obj->geometry.y;
obj_area.x2 = obj->geometry.x + obj->geometry.width - 1;
obj_area.y2 = obj->geometry.y + obj->geometry.height - 1;
obj->state.dirty = true;
gfx_invalidate_area_disp(obj->disp, &obj_area);
}
void gfx_refr_update_layout_dirty(gfx_disp_t *disp)
{
if (disp == NULL || disp->child_list == NULL) {
return;
}
gfx_obj_child_t *child_node = disp->child_list;
while (child_node != NULL) {
gfx_obj_t *obj = (gfx_obj_t *)child_node->src;
if (obj != NULL && obj->state.layout_dirty && obj->align.enabled) {
gfx_coord_t old_x = obj->geometry.x;
gfx_coord_t old_y = obj->geometry.y;
gfx_obj_invalidate(obj);
gfx_obj_calc_pos_in_parent(obj);
gfx_obj_invalidate(obj);
GFX_LOGD(TAG,
"layout update: obj=%p (%d,%d) -> (%d,%d)",
obj,
old_x,
old_y,
obj->geometry.x,
obj->geometry.y);
obj->state.layout_dirty = false;
}
child_node = child_node->next;
}
}

View File

@ -0,0 +1,112 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "core/runtime/gfx_core_priv.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Invalidate an area for a specific display (internal)
*/
void gfx_invalidate_area_disp(gfx_disp_t *disp, const gfx_area_t *area_p);
/**
* @brief Invalidate an area globally (mark it for redraw) - applies to first display
* @param handle Graphics handle
* @param area Pointer to the area to invalidate, or NULL to clear all invalid areas
*
* This function adds an area to the global dirty area list.
* - If area is NULL, clears all invalid areas on all displays
* - Areas are automatically clipped to screen bounds
* - Overlapping/adjacent areas are merged
* - If buffer is full, marks entire screen as dirty
*/
void gfx_invalidate_area(gfx_handle_t handle, const gfx_area_t *area);
/**
* @brief Invalidate an object's area (convenience function)
* @param obj Pointer to the object to invalidate
*
* Marks the entire object bounds as dirty in the global invalidation list.
*/
void gfx_obj_invalidate(gfx_obj_t *obj);
/**
* @brief Update layout for all objects marked as layout dirty on a display
* @param disp Display to update
*/
void gfx_refr_update_layout_dirty(gfx_disp_t *disp);
/**
* @brief Merge overlapping/adjacent dirty areas to minimize redraw regions
* @param disp Display containing dirty areas
*/
void gfx_refr_merge_areas(gfx_disp_t *disp);
/* Area utility functions (merged from gfx_area.h) */
/**
* @brief Copy area from src to dest
* @param dest Destination area
* @param src Source area
*/
void gfx_area_copy(gfx_area_t *dest, const gfx_area_t *src);
/**
* @brief Check if area_in is fully contained within area_parent
* @param area_in Area to check
* @param area_parent Parent area
* @return true if area_in is completely inside area_parent
*/
bool gfx_area_is_in(const gfx_area_t *area_in, const gfx_area_t *area_parent);
/**
* @brief Get intersection of two areas
* @param result Result area (intersection)
* @param a1 First area
* @param a2 Second area
* @return true if areas intersect, false otherwise
*/
bool gfx_area_intersect(gfx_area_t *result, const gfx_area_t *a1, const gfx_area_t *a2);
/**
* @brief Get intersection of two half-open areas [x1, x2) x [y1, y2)
* @param result Result area (intersection)
* @param a1 First area with exclusive x2/y2
* @param a2 Second area with exclusive x2/y2
* @return true if areas intersect, false otherwise
*/
bool gfx_area_intersect_exclusive(gfx_area_t *result, const gfx_area_t *a1, const gfx_area_t *a2);
/**
* @brief Get the size (area) of a rectangular region
* @param area Area to calculate size for
* @return Size in pixels (width * height)
*/
uint32_t gfx_area_get_size(const gfx_area_t *area);
/**
* @brief Check if two areas are on each other (overlap or touch)
* @param a1 First area
* @param a2 Second area
* @return true if areas overlap or are adjacent (touch)
*/
bool gfx_area_is_on(const gfx_area_t *a1, const gfx_area_t *a2);
/**
* @brief Join two areas into a larger area (bounding box)
* @param result Result area (bounding box of a1 and a2)
* @param a1 First area
* @param a2 Second area
*/
void gfx_area_join(gfx_area_t *result, const gfx_area_t *a1, const gfx_area_t *a2);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,384 @@
/*
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*********************
* INCLUDES
*********************/
#include <string.h>
#include <inttypes.h>
#include "esp_timer.h"
#include "esp_log.h"
#define GFX_LOG_MODULE GFX_LOG_MODULE_RENDER
#include "common/gfx_log_priv.h"
#include "core/display/gfx_refr_priv.h"
#include "core/display/gfx_render_priv.h"
#include "core/draw/gfx_blend_priv.h"
#include "core/runtime/gfx_timer_priv.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC VARIABLES
**********************/
static const char *TAG = "render";
/**********************
* STATIC PROTOTYPES
**********************/
static void gfx_render_sync_dirty_areas(gfx_disp_t *disp);
/**********************
* STATIC FUNCTIONS
**********************/
static void gfx_render_sync_dirty_areas(gfx_disp_t *disp)
{
if (!disp->flags.full_frame || disp->buf.buf2 == NULL || disp->sync_pending.count == 0) {
return;
}
uint16_t *dst_screen_buf = disp->buf.buf_act;
uint16_t *src_screen_buf = (disp->buf.buf_act == disp->buf.buf1) ? disp->buf.buf2 : disp->buf.buf1;
uint32_t stride = disp->res.h_res;
const size_t px_size = sizeof(uint16_t);
for (uint8_t i = 0; i < disp->sync_pending.count; i++) {
const gfx_area_t *a = &disp->sync_pending.areas[i];
bool covered = false;
for (uint8_t j = 0; j < disp->dirty.count && !covered; j++) {
if (disp->dirty.merged[j]) {
continue;
}
if (gfx_area_is_in(a, &disp->dirty.areas[j])) {
covered = true;
}
}
if (covered) {
continue;
}
uint32_t w = (uint32_t)(a->x2 - a->x1 + 1);
uint32_t h = (uint32_t)(a->y2 - a->y1 + 1);
for (uint32_t y = 0; y < h; y++) {
size_t offset = (size_t)(a->y1 + (gfx_coord_t)y) * stride + (size_t)a->x1;
memcpy(dst_screen_buf + offset, src_screen_buf + offset, w * px_size);
}
}
}
/**********************
* PUBLIC FUNCTIONS
**********************/
void gfx_render_draw_child_objects(gfx_disp_t *disp, const gfx_draw_ctx_t *ctx)
{
if (disp == NULL || disp->child_list == NULL || ctx == NULL) {
return;
}
gfx_obj_child_t *child_node = disp->child_list;
while (child_node != NULL) {
gfx_obj_t *obj = (gfx_obj_t *)child_node->src;
if (!obj->state.is_visible) {
child_node = child_node->next;
continue;
}
if (obj->vfunc.draw) {
obj->vfunc.draw(obj, ctx);
}
child_node = child_node->next;
}
}
void gfx_render_update_child_objects(gfx_disp_t *disp)
{
if (disp == NULL || disp->child_list == NULL) {
return;
}
gfx_obj_child_t *child_node = disp->child_list;
while (child_node != NULL) {
gfx_obj_t *obj = (gfx_obj_t *)child_node->src;
if (!obj->state.is_visible) {
child_node = child_node->next;
continue;
}
if (obj->vfunc.update) {
obj->vfunc.update(obj);
}
child_node = child_node->next;
}
}
uint32_t gfx_render_area_summary(gfx_disp_t *disp)
{
uint32_t total_dirty_pixels = 0;
if (disp == NULL) {
return 0;
}
for (uint8_t i = 0; i < disp->dirty.count; i++) {
if (disp->dirty.merged[i]) {
continue;
}
gfx_area_t *area = &disp->dirty.areas[i];
uint32_t area_size = gfx_area_get_size(area);
total_dirty_pixels += area_size;
// GFX_LOGD(TAG, "Draw area [%d]: (%d,%d)->(%d,%d) %dx%d",
// i, area->x1, area->y1, area->x2, area->y2,
// area->x2 - area->x1 + 1, area->y2 - area->y1 + 1);
}
return total_dirty_pixels;
}
void gfx_render_part_area(gfx_disp_t *disp, gfx_area_t *area, uint8_t area_idx, bool is_last_area)
{
if (disp == NULL || area == NULL) {
return;
}
if (area->x2 < area->x1 || area->y2 < area->y1) {
GFX_LOGE(TAG, "render area[%d]: invalid bounds (%d,%d)-(%d,%d)", area_idx,
area->x1, area->y1, area->x2, area->y2);
return;
}
uint32_t area_w = (uint32_t)(area->x2 - area->x1 + 1);
uint32_t row_h = disp->buf.buf_pixels / area_w;
if (row_h == 0) {
GFX_LOGE(TAG, "render area[%d]: width %" PRIu32 " exceeds buffer, skipping", area_idx, area_w);
return;
}
gfx_disp_flush_cb_t flush_cb = disp->cb.flush_cb;
if (flush_cb != NULL && disp->sync.event_group == NULL) {
GFX_LOGE(TAG, "render area[%d]: flush callback is set but event group is NULL", area_idx);
return;
}
disp->render.flushing_last = false;
gfx_coord_t cur_y = area->y1;
while (cur_y <= area->y2) {
int64_t render_start_us;
int64_t flush_start_us;
gfx_coord_t chunk_x1 = area->x1;
gfx_coord_t chunk_y1 = cur_y;
gfx_coord_t chunk_x2 = area->x2 + 1;
gfx_coord_t chunk_y2 = cur_y + (gfx_coord_t)row_h;
if (chunk_y2 > area->y2 + 1) {
chunk_y2 = area->y2 + 1;
}
uint16_t *buf = disp->buf.buf_act;
int dest_stride = disp->flags.full_frame ? disp->res.h_res : (chunk_x2 - chunk_x1);
gfx_area_t buf_area;
if (disp->flags.full_frame) {
buf_area.x1 = 0;
buf_area.y1 = 0;
buf_area.x2 = (gfx_coord_t)disp->res.h_res;
buf_area.y2 = (gfx_coord_t)disp->res.v_res;
} else {
buf_area.x1 = chunk_x1;
buf_area.y1 = chunk_y1;
buf_area.x2 = chunk_x2;
buf_area.y2 = chunk_y2;
}
gfx_draw_ctx_t draw_ctx = {
.buf = buf,
.buf_area = buf_area,
.clip_area = { chunk_x1, chunk_y1, chunk_x2, chunk_y2 },
.stride = dest_stride,
.swap = disp->flags.swap,
};
render_start_us = esp_timer_get_time();
if (disp->style.bg_enable) {
uint16_t bg = gfx_color_to_native_u16(disp->style.bg_color, disp->flags.swap);
if (disp->flags.full_frame) {
gfx_area_t fill_area = { chunk_x1, chunk_y1, chunk_x2, chunk_y2 }; /* exclusive x2,y2 */
gfx_sw_blend_fill_area(buf, (gfx_coord_t)disp->res.h_res, &fill_area, bg);
} else {
gfx_area_t fill_area = { 0, 0, chunk_x2 - chunk_x1, chunk_y2 - chunk_y1 };
gfx_sw_blend_fill_area(buf, chunk_x2 - chunk_x1, &fill_area, bg);
}
}
gfx_render_draw_child_objects(disp, &draw_ctx);
disp->render.render_time_us += (uint64_t)(esp_timer_get_time() - render_start_us);
if (flush_cb != NULL) {
xEventGroupClearBits(disp->sync.event_group, WAIT_FLUSH_DONE);
// uint32_t chunk_px = area_w * (uint32_t)(chunk_y2 - chunk_y1);
bool is_last_chunk = (chunk_y2 >= area->y2 + 1);
disp->render.flushing_last = is_last_chunk && is_last_area;
// GFX_LOGD(TAG, "Flush: (%d,%d)-(%d,%d) %" PRIu32 " px%s",
// chunk_x1, chunk_y1, chunk_x2 - 1, chunk_y2 - 1, chunk_px,
// disp->render.flushing_last ? " (last)" : "");
flush_start_us = esp_timer_get_time();
flush_cb(disp, chunk_x1, chunk_y1, chunk_x2, chunk_y2, buf);
xEventGroupWaitBits(disp->sync.event_group, WAIT_FLUSH_DONE, pdTRUE, pdFALSE, portMAX_DELAY);
disp->render.flush_time_us += (uint64_t)(esp_timer_get_time() - flush_start_us);
disp->render.flush_count++;
if (disp->buf.buf2 != NULL && (!disp->flags.full_frame || disp->render.flushing_last)) {
disp->buf.buf_act = (disp->buf.buf_act == disp->buf.buf1) ? disp->buf.buf2 : disp->buf.buf1;
}
}
cur_y = chunk_y2;
}
}
/**
* @brief Render all dirty areas
* @param disp Display
*/
void gfx_render_dirty_areas(gfx_disp_t *disp)
{
if (disp == NULL) {
return;
}
disp->render.render_time_us = 0;
disp->render.flush_time_us = 0;
disp->render.flush_count = 0;
gfx_sw_blend_perf_reset(&disp->render.blend);
gfx_sw_blend_perf_bind(&disp->render.blend);
gfx_render_sync_dirty_areas(disp);
uint8_t last_area_idx = 0;
for (uint8_t i = 0; i < disp->dirty.count; i++) {
if (!disp->dirty.merged[i]) {
last_area_idx = i;
}
}
uint8_t sync_points = 0;
for (uint8_t i = 0; i < disp->dirty.count; i++) {
if (disp->dirty.merged[i]) {
continue;
}
gfx_area_t *area = &disp->dirty.areas[i];
bool is_last_area = (i == last_area_idx);
gfx_render_part_area(disp, area, i, is_last_area);
sync_points++;
gfx_area_copy(&disp->sync_pending.areas[sync_points], area);
}
gfx_sw_blend_perf_unbind();
disp->sync_pending.count = sync_points;
}
/**
* @brief Cleanup after rendering - swap buffers and clear dirty flags
* @param disp Display
*/
void gfx_render_cleanup(gfx_disp_t *disp)
{
if (disp == NULL) {
return;
}
if (disp->dirty.count > 0) {
gfx_invalidate_area_disp(disp, NULL);
}
}
/**
* @brief Handle rendering of all objects in the scene
* @param ctx Player context
* @return true if any display was rendered, false otherwise
*/
bool gfx_render_handler(gfx_core_context_t *ctx)
{
static const uint32_t fps_sample_window = 100;
static uint32_t fps_samples = 0;
static uint32_t fps_elapsed_ms = 0;
static uint32_t last_tick_ms = 0;
uint32_t now_ms = gfx_timer_tick_get();
if (last_tick_ms == 0) {
last_tick_ms = now_ms;
} else {
uint32_t elapsed_ms = gfx_timer_tick_elaps(last_tick_ms);
fps_samples++;
fps_elapsed_ms += elapsed_ms;
last_tick_ms = now_ms;
if (fps_samples >= fps_sample_window) {
gfx_timer_mgr_t *mgr = &ctx->timer_mgr;
mgr->actual_fps = (fps_samples * 1000) / fps_elapsed_ms;
fps_samples = 0;
fps_elapsed_ms = 0;
}
}
bool did_render = false;
for (gfx_disp_t *disp = ctx->disp; disp != NULL; disp = disp->next) {
int64_t frame_start_us = esp_timer_get_time();
gfx_refr_update_layout_dirty(disp);
if (disp->dirty.count > 1) {
gfx_refr_merge_areas(disp);
} else if (disp->dirty.count == 0) {
continue;
}
gfx_render_update_child_objects(disp);
uint32_t dirty_px = gfx_render_area_summary(disp);
gfx_render_dirty_areas(disp);
uint64_t frame_time_us = (uint64_t)(esp_timer_get_time() - frame_start_us);
disp->render.dirty_pixels = dirty_px;
disp->render.frame_time_us = frame_time_us;
if (dirty_px > 0) {
did_render = true;
uint32_t screen_px = disp->res.h_res * disp->res.v_res;
float dirty_pct = (dirty_px * 100.0f) / (float)screen_px;
GFX_LOGD(TAG,
"%.1f%% (%" PRIu64 "ms) (%" PRIu64 "|%" PRIu64 ")",
dirty_pct,
frame_time_us / 1000,
disp->render.render_time_us / 1000,
disp->render.flush_time_us / 1000);
}
gfx_render_cleanup(disp);
}
return did_render;
}

View File

@ -0,0 +1,58 @@
/*
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "core/runtime/gfx_core_priv.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Handle rendering of all objects in the scene (iterates over all displays)
* @param ctx Player context
* @return true if rendering was performed, false otherwise
*/
bool gfx_render_handler(gfx_core_context_t *ctx);
/**
* @brief Render all dirty areas for one display
*/
void gfx_render_dirty_areas(gfx_disp_t *disp);
/**
* @brief Render a single dirty area with dynamic height-based blocking
* @param is_last_area true if this is the last dirty area in the list (flushing_last = last chunk of this area AND is_last_area)
*/
void gfx_render_part_area(gfx_disp_t *disp, gfx_area_t *area, uint8_t area_idx, bool is_last_area);
/**
* @brief Cleanup after rendering - swap buffers and clear dirty flags for one display
*/
void gfx_render_cleanup(gfx_disp_t *disp);
/**
* @brief Print summary of dirty areas for one display
* @return Total dirty pixels
*/
uint32_t gfx_render_area_summary(gfx_disp_t *disp);
/**
* @brief Draw child objects for one display using draw context (buf_area + clip_area)
* @param ctx Draw context: buf, buf_area, clip_area, stride, swap
* buf_area and clip_area use half-open bounds [x1, x2) x [y1, y2)
*/
void gfx_render_draw_child_objects(gfx_disp_t *disp, const gfx_draw_ctx_t *ctx);
/**
* @brief Update child objects for one display
*/
void gfx_render_update_child_objects(gfx_disp_t *disp);
#ifdef __cplusplus
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,174 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stddef.h>
#include "core/gfx_types.h"
#include "core/gfx_disp.h"
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* DEFINES
*********************/
/**
* Bit flag OR-ed into the `internal_edges` parameter of
* gfx_sw_blend_img_triangle_draw to request inward-only edge AA.
* When set, the rasterizer fades pixels near non-internal outer edges
* from inside the triangle rather than drawing semi-transparent pixels
* outside it. This prevents visible "bleed" on thin strokes (eyebrows).
*/
#define GFX_BLEND_TRI_AA_INWARD 0x80
#define GFX_BLEND_MAX_EXTRA_AA_EDGES 2
/**********************
* TYPEDEFS
**********************/
typedef struct {
int32_t x;
int32_t y;
gfx_coord_t u;
gfx_coord_t v;
} gfx_sw_blend_img_vertex_t;
/**
* Extra directed edge for cross-triangle inward AA.
* Represents edge AB in mesh subpixel coordinates.
*/
typedef struct {
int32_t a; /**< dy: B.y - A.y */
int32_t b; /**< -dx: A.x - B.x */
int32_t len; /**< sqrt(a² + b²) */
int32_t vx; /**< reference vertex x (A.x, mesh subpixel) */
int32_t vy; /**< reference vertex y (A.y, mesh subpixel) */
} gfx_sw_blend_aa_edge_t;
/**********************
* PRIVATE FUNCTIONS
**********************/
/**
* @brief Fast fill buffer with background color
* @param buf Pointer to uint16_t buffer
* @param color 16-bit color value
* @param pixels Number of pixels to fill
*/
void gfx_sw_blend_fill(uint16_t *buf, uint16_t color, size_t pixels);
/**
* @brief Fill a rectangle in dest buffer (standard blend form: dest_buf + stride + area)
* @param dest_buf Destination buffer (uint16_t)
* @param dest_stride Row stride in pixels
* @param area Area to fill (x1,y1,x2,y2 exclusive end)
* @param color 16-bit color value in native framebuffer byte order
*/
void gfx_sw_blend_fill_area(uint16_t *dest_buf, gfx_coord_t dest_stride,
const gfx_area_t *area, uint16_t color);
/**
* @brief Mix two colors with a given mix ratio (internal)
* @param c1 First color
* @param c2 Second color
* @param mix Mix ratio (0-255)
* @param swap Whether to swap color format
* @return Mixed color
*/
gfx_color_t gfx_blend_color_mix(gfx_color_t c1, gfx_color_t c2, uint8_t mix, bool swap);
/**********************
* GLOBAL PROTOTYPES
**********************/
/*=====================
* Software blending functions
*====================*/
/**
* @brief Draw a blended color onto a destination buffer
* @param dest_buf Pointer to the destination buffer where the color will be drawn
* @param dest_stride Stride (width) of the destination buffer
* @param mask Pointer to the mask buffer, if any
* @param mask_stride Stride (width) of the mask buffer
* @param clip_area Pointer to the clipping area, which limits the area to draw
* @param color The color to draw in gfx_color_t type
* @param opa The opacity of the color to draw (0-255)
* @param swap Whether to swap the color format
*/
void gfx_sw_blend_draw(gfx_color_t *dest_buf, gfx_coord_t dest_stride,
const gfx_opa_t *mask, gfx_coord_t mask_stride,
gfx_area_t *clip_area, gfx_color_t color, gfx_opa_t opa, bool swap);
/**
* @brief Draw a blended image onto a destination buffer
* @param dest_buf Pointer to the destination buffer where the image will be drawn
* @param dest_stride Stride (width) of the destination buffer
* @param src_buf Pointer to the source image buffer
* @param src_stride Stride (width) of the source image buffer
* @param mask Pointer to the mask buffer, if any
* @param mask_stride Stride (width) of the mask buffer
* @param clip_area Pointer to the clipping area, which limits the area to draw
* @param swap Whether to swap the color format
*/
void gfx_sw_blend_img_draw(gfx_color_t *dest_buf, gfx_coord_t dest_stride,
const gfx_color_t *src_buf, gfx_coord_t src_stride,
const gfx_opa_t *mask, gfx_coord_t mask_stride,
gfx_area_t *clip_area, bool swap);
/**
* @brief Draw a textured triangle with edge anti-aliasing
*
* @param internal_edges Bitmask of edges shared with adjacent triangles.
* Bit 0 = edge 0 (v1v2), bit 1 = edge 1 (v2v0), bit 2 = edge 2 (v0v1).
* AA is suppressed on flagged edges to prevent dark-seam artifacts.
* Pass 0 for standalone triangles (full AA on all edges).
* @param extra_aa_edges Optional array of extra directed edges for cross-triangle
* inward AA distance (NULL when not needed).
* @param extra_aa_count Number of entries in extra_aa_edges (0..MAX_EXTRA_AA_EDGES).
*/
void gfx_sw_blend_img_triangle_draw(gfx_color_t *dest_buf, gfx_coord_t dest_stride,
const gfx_area_t *buf_area, const gfx_area_t *clip_area,
const gfx_color_t *src_buf, gfx_coord_t src_stride, gfx_coord_t src_height,
const gfx_opa_t *mask, gfx_coord_t mask_stride,
gfx_opa_t opa,
const gfx_sw_blend_img_vertex_t *v0,
const gfx_sw_blend_img_vertex_t *v1,
const gfx_sw_blend_img_vertex_t *v2,
uint8_t internal_edges,
const gfx_sw_blend_aa_edge_t *extra_aa_edges,
uint8_t extra_aa_count,
bool swap);
/**
* @brief Scanline polygon fill with edge anti-aliasing.
*
* Fills a closed polygon defined by vertex arrays with a solid color.
* Uses even-odd fill rule. Vertices are in mesh subpixel coordinates
* (same as gfx_sw_blend_img_vertex_t x/y).
*
* Designed for stroke outlines where no texture mapping is needed.
*/
void gfx_sw_blend_polygon_fill(gfx_color_t *dest_buf, gfx_coord_t dest_stride,
const gfx_area_t *buf_area, const gfx_area_t *clip_area,
gfx_color_t color,
gfx_opa_t opa,
const int32_t *vx, const int32_t *vy,
int vertex_count,
bool swap);
void gfx_sw_blend_perf_reset(gfx_blend_perf_stats_t *stats);
void gfx_sw_blend_perf_bind(gfx_blend_perf_stats_t *stats);
void gfx_sw_blend_perf_unbind(void);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,194 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*********************
* INCLUDES
*********************/
#include <stddef.h>
#include "core/draw/gfx_blend_priv.h"
#include "core/draw/gfx_sw_draw_priv.h"
/**********************
* STATIC FUNCTIONS
**********************/
static bool gfx_sw_draw_get_pixel_ptr(gfx_color_t **pixel,
gfx_color_t *dest_buf,
gfx_coord_t dest_stride,
const gfx_area_t *buf_area,
const gfx_area_t *clip_area,
gfx_coord_t x,
gfx_coord_t y)
{
if (pixel == NULL || dest_buf == NULL || buf_area == NULL || clip_area == NULL) {
return false;
}
if (x < clip_area->x1 || x >= clip_area->x2 || y < clip_area->y1 || y >= clip_area->y2) {
return false;
}
if (x < buf_area->x1 || x >= buf_area->x2 || y < buf_area->y1 || y >= buf_area->y2) {
return false;
}
*pixel = dest_buf + (size_t)(y - buf_area->y1) * dest_stride + (size_t)(x - buf_area->x1);
return true;
}
/**********************
* PUBLIC FUNCTIONS
**********************/
void gfx_sw_draw_point(gfx_color_t *dest_buf, gfx_coord_t dest_stride,
const gfx_area_t *buf_area, const gfx_area_t *clip_area,
gfx_coord_t x, gfx_coord_t y,
gfx_color_t color, gfx_opa_t opa, bool swap)
{
gfx_color_t *pixel = NULL;
gfx_color_t draw_color = color;
if (!gfx_sw_draw_get_pixel_ptr(&pixel, dest_buf, dest_stride, buf_area, clip_area, x, y)) {
return;
}
if (opa >= 0xFF) {
draw_color.full = gfx_color_to_native_u16(draw_color, swap);
*pixel = draw_color;
} else if (opa > 0) {
*pixel = gfx_blend_color_mix(color, *pixel, opa, swap);
}
}
void gfx_sw_draw_hline(gfx_color_t *dest_buf, gfx_coord_t dest_stride,
const gfx_area_t *buf_area, const gfx_area_t *clip_area,
gfx_coord_t x1, gfx_coord_t x2, gfx_coord_t y,
gfx_color_t color, gfx_opa_t opa, bool swap)
{
gfx_color_t draw_color = color;
if (dest_buf == NULL || buf_area == NULL || clip_area == NULL || x2 <= x1 || opa == 0) {
return;
}
/* Clip against both clip_area and buf_area in one pass */
gfx_coord_t draw_x1 = (x1 > clip_area->x1) ? x1 : clip_area->x1;
gfx_coord_t draw_x2 = (x2 < clip_area->x2) ? x2 : clip_area->x2;
if (draw_x1 < buf_area->x1) {
draw_x1 = buf_area->x1;
}
if (draw_x2 > buf_area->x2) {
draw_x2 = buf_area->x2;
}
if (draw_x2 <= draw_x1) {
return;
}
if (y < clip_area->y1 || y >= clip_area->y2 || y < buf_area->y1 || y >= buf_area->y2) {
return;
}
gfx_color_t *pixel = dest_buf + (size_t)(y - buf_area->y1) * dest_stride
+ (size_t)(draw_x1 - buf_area->x1);
size_t count = (size_t)(draw_x2 - draw_x1);
if (opa >= 0xFF) {
draw_color.full = gfx_color_to_native_u16(draw_color, swap);
gfx_sw_blend_fill((uint16_t *)pixel, draw_color.full, count);
} else {
for (size_t i = 0; i < count; ++i) {
pixel[i] = gfx_blend_color_mix(color, pixel[i], opa, swap);
}
}
}
void gfx_sw_draw_vline(gfx_color_t *dest_buf, gfx_coord_t dest_stride,
const gfx_area_t *buf_area, const gfx_area_t *clip_area,
gfx_coord_t x, gfx_coord_t y1, gfx_coord_t y2,
gfx_color_t color, gfx_opa_t opa, bool swap)
{
gfx_color_t draw_color = color;
if (dest_buf == NULL || buf_area == NULL || clip_area == NULL || y2 <= y1 || opa == 0) {
return;
}
if (x < clip_area->x1 || x >= clip_area->x2 || x < buf_area->x1 || x >= buf_area->x2) {
return;
}
/* Clip against both clip_area and buf_area in one pass */
gfx_coord_t draw_y1 = (y1 > clip_area->y1) ? y1 : clip_area->y1;
gfx_coord_t draw_y2 = (y2 < clip_area->y2) ? y2 : clip_area->y2;
if (draw_y1 < buf_area->y1) {
draw_y1 = buf_area->y1;
}
if (draw_y2 > buf_area->y2) {
draw_y2 = buf_area->y2;
}
if (draw_y2 <= draw_y1) {
return;
}
gfx_color_t *pixel = dest_buf + (size_t)(draw_y1 - buf_area->y1) * dest_stride
+ (size_t)(x - buf_area->x1);
if (opa >= 0xFF) {
draw_color.full = gfx_color_to_native_u16(draw_color, swap);
for (gfx_coord_t row = draw_y1; row < draw_y2; ++row) {
*pixel = draw_color;
pixel += dest_stride;
}
} else {
for (gfx_coord_t row = draw_y1; row < draw_y2; ++row) {
*pixel = gfx_blend_color_mix(color, *pixel, opa, swap);
pixel += dest_stride;
}
}
}
void gfx_sw_draw_rect_stroke(gfx_color_t *dest_buf, gfx_coord_t dest_stride,
const gfx_area_t *buf_area, const gfx_area_t *clip_area,
const gfx_area_t *rect, uint16_t line_width,
gfx_color_t color, gfx_opa_t opa, bool swap)
{
gfx_coord_t max_line_w;
gfx_coord_t max_line_h;
gfx_coord_t stroke_w;
if (dest_buf == NULL || buf_area == NULL || clip_area == NULL || rect == NULL || line_width == 0) {
return;
}
if (rect->x2 <= rect->x1 || rect->y2 <= rect->y1) {
return;
}
max_line_w = (gfx_coord_t)((line_width * 2U <= (uint16_t)(rect->x2 - rect->x1)) ? line_width : ((rect->x2 - rect->x1) / 2));
max_line_h = (gfx_coord_t)((line_width * 2U <= (uint16_t)(rect->y2 - rect->y1)) ? line_width : ((rect->y2 - rect->y1) / 2));
stroke_w = (max_line_w < max_line_h) ? max_line_w : max_line_h;
if (stroke_w <= 0) {
return;
}
for (gfx_coord_t i = 0; i < stroke_w; ++i) {
gfx_sw_draw_hline(dest_buf, dest_stride, buf_area, clip_area,
rect->x1 + i, rect->x2 - i, rect->y1 + i,
color, opa, swap);
gfx_sw_draw_hline(dest_buf, dest_stride, buf_area, clip_area,
rect->x1 + i, rect->x2 - i, rect->y2 - 1 - i,
color, opa, swap);
gfx_sw_draw_vline(dest_buf, dest_stride, buf_area, clip_area,
rect->x1 + i, rect->y1 + i, rect->y2 - i,
color, opa, swap);
gfx_sw_draw_vline(dest_buf, dest_stride, buf_area, clip_area,
rect->x2 - 1 - i, rect->y1 + i, rect->y2 - i,
color, opa, swap);
}
}

View File

@ -0,0 +1,37 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "core/gfx_types.h"
#ifdef __cplusplus
extern "C" {
#endif
void gfx_sw_draw_point(gfx_color_t *dest_buf, gfx_coord_t dest_stride,
const gfx_area_t *buf_area, const gfx_area_t *clip_area,
gfx_coord_t x, gfx_coord_t y,
gfx_color_t color, gfx_opa_t opa, bool swap);
void gfx_sw_draw_hline(gfx_color_t *dest_buf, gfx_coord_t dest_stride,
const gfx_area_t *buf_area, const gfx_area_t *clip_area,
gfx_coord_t x1, gfx_coord_t x2, gfx_coord_t y,
gfx_color_t color, gfx_opa_t opa, bool swap);
void gfx_sw_draw_vline(gfx_color_t *dest_buf, gfx_coord_t dest_stride,
const gfx_area_t *buf_area, const gfx_area_t *clip_area,
gfx_coord_t x, gfx_coord_t y1, gfx_coord_t y2,
gfx_color_t color, gfx_opa_t opa, bool swap);
void gfx_sw_draw_rect_stroke(gfx_color_t *dest_buf, gfx_coord_t dest_stride,
const gfx_area_t *buf_area, const gfx_area_t *clip_area,
const gfx_area_t *rect, uint16_t line_width,
gfx_color_t color, gfx_opa_t opa, bool swap);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,451 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*********************
* INCLUDES
*********************/
#include <stdlib.h>
#include "esp_log.h"
#define GFX_LOG_MODULE GFX_LOG_MODULE_OBJ
#include "common/gfx_log_priv.h"
#include "common/gfx_comm.h"
#include "core/gfx_obj.h"
#include "core/display/gfx_refr_priv.h"
#include "core/runtime/gfx_core_priv.h"
/**********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC VARIABLES
**********************/
static const char *TAG = "obj";
/**********************
* STATIC PROTOTYPES
**********************/
static void gfx_obj_notify_aligned_dependents(gfx_obj_t *obj, uint8_t depth);
static void gfx_obj_detach_aligned_dependents(gfx_obj_t *obj);
static void gfx_obj_calc_pos_in_parent_internal(gfx_obj_t *obj, uint8_t depth);
/**********************
* STATIC FUNCTIONS
**********************/
static void gfx_obj_notify_aligned_dependents(gfx_obj_t *obj, uint8_t depth)
{
if (obj == NULL || obj->disp == NULL || depth > 8) {
return;
}
for (gfx_obj_child_t *child = obj->disp->child_list; child != NULL; child = child->next) {
gfx_obj_t *child_obj = (gfx_obj_t *)child->src;
if (child_obj == NULL || child_obj == obj) {
continue;
}
if (child_obj->align.enabled && child_obj->align.target == obj) {
child_obj->state.layout_dirty = true;
gfx_obj_invalidate(child_obj);
gfx_obj_notify_aligned_dependents(child_obj, depth + 1);
}
}
}
static void gfx_obj_detach_aligned_dependents(gfx_obj_t *obj)
{
if (obj == NULL || obj->disp == NULL) {
return;
}
for (gfx_obj_child_t *child = obj->disp->child_list; child != NULL; child = child->next) {
gfx_obj_t *child_obj = (gfx_obj_t *)child->src;
if (child_obj == NULL || child_obj == obj) {
continue;
}
if (child_obj->align.target == obj) {
child_obj->align.target = NULL;
child_obj->state.layout_dirty = true;
gfx_obj_invalidate(child_obj);
gfx_obj_notify_aligned_dependents(child_obj, 1);
}
}
}
static void gfx_obj_calc_pos_in_parent_internal(gfx_obj_t *obj, uint8_t depth)
{
gfx_coord_t origin_x = 0;
gfx_coord_t origin_y = 0;
uint32_t parent_w;
uint32_t parent_h;
GFX_RETURN_IF_NULL_VOID(obj);
if (depth > 8) {
GFX_LOGW(TAG, "align depth too large, stop resolving");
return;
}
parent_w = gfx_disp_get_hor_res(obj->disp);
parent_h = gfx_disp_get_ver_res(obj->disp);
if (obj->align.target != NULL && obj->align.target != obj) {
gfx_obj_t *target = obj->align.target;
if (target->disp == obj->disp) {
gfx_obj_calc_pos_in_parent_internal(target, depth + 1);
origin_x = target->geometry.x;
origin_y = target->geometry.y;
parent_w = target->geometry.width;
parent_h = target->geometry.height;
}
}
gfx_obj_cal_aligned_pos(obj, parent_w, parent_h, &obj->geometry.x, &obj->geometry.y);
obj->geometry.x += origin_x;
obj->geometry.y += origin_y;
}
/**********************
* PUBLIC FUNCTIONS
**********************/
/* Generic object setters */
esp_err_t gfx_obj_set_pos(gfx_obj_t *obj, gfx_coord_t x, gfx_coord_t y)
{
GFX_RETURN_IF_NULL(obj, ESP_ERR_INVALID_ARG);
//invalidate the old position
gfx_obj_invalidate(obj);
obj->geometry.x = x;
obj->geometry.y = y;
obj->align.enabled = false;
obj->align.target = NULL;
//invalidate the new position
gfx_obj_invalidate(obj);
gfx_obj_notify_aligned_dependents(obj, 0);
GFX_LOGD(TAG, "Set object position: (%d, %d)", x, y);
return ESP_OK;
}
esp_err_t gfx_obj_set_size(gfx_obj_t *obj, uint16_t w, uint16_t h)
{
GFX_RETURN_IF_NULL(obj, ESP_ERR_INVALID_ARG);
if (obj->type == GFX_OBJ_TYPE_ANIMATION ||
obj->type == GFX_OBJ_TYPE_IMAGE ||
obj->type == GFX_OBJ_TYPE_MESH_IMAGE ||
obj->type == GFX_OBJ_TYPE_QRCODE) {
GFX_LOGD(TAG, "Set size is not useful for type: %d", obj->type);
} else {
//invalidate the old size
gfx_obj_invalidate(obj);
obj->geometry.width = w;
obj->geometry.height = h;
gfx_obj_update_layout(obj);
gfx_obj_invalidate(obj);
gfx_obj_notify_aligned_dependents(obj, 0);
}
GFX_LOGD(TAG, "Set object size: %dx%d", w, h);
return ESP_OK;
}
esp_err_t gfx_obj_align(gfx_obj_t *obj, uint8_t align, gfx_coord_t x_ofs, gfx_coord_t y_ofs)
{
GFX_RETURN_IF_NULL(obj, ESP_ERR_INVALID_ARG);
GFX_RETURN_IF_NULL(obj->disp, ESP_ERR_INVALID_STATE);
if (align > GFX_ALIGN_OUT_BOTTOM_RIGHT) {
GFX_LOGW(TAG, "Unknown alignment type: %d", align);
return ESP_ERR_INVALID_ARG;
}
// Invalidate old position first
gfx_obj_invalidate(obj);
// Update alignment parameters and enable alignment
obj->align.type = align;
obj->align.x_ofs = x_ofs;
obj->align.y_ofs = y_ofs;
obj->align.target = NULL;
obj->align.enabled = true;
gfx_obj_update_layout(obj);
GFX_LOGD(TAG, "Set object alignment: type=%d, offset=(%d, %d)", align, x_ofs, y_ofs);
return ESP_OK;
}
esp_err_t gfx_obj_align_to(gfx_obj_t *obj, gfx_obj_t *base, uint8_t align, gfx_coord_t x_ofs, gfx_coord_t y_ofs)
{
GFX_RETURN_IF_NULL(obj, ESP_ERR_INVALID_ARG);
GFX_RETURN_IF_NULL(obj->disp, ESP_ERR_INVALID_STATE);
if (align > GFX_ALIGN_OUT_BOTTOM_RIGHT) {
GFX_LOGW(TAG, "Unknown alignment type: %d", align);
return ESP_ERR_INVALID_ARG;
}
if (base != NULL && base->disp != obj->disp) {
GFX_LOGW(TAG, "align_to base must be on same display");
return ESP_ERR_INVALID_ARG;
}
if (base == obj) {
GFX_LOGW(TAG, "align_to base cannot be self");
return ESP_ERR_INVALID_ARG;
}
gfx_obj_invalidate(obj);
obj->align.type = align;
obj->align.x_ofs = x_ofs;
obj->align.y_ofs = y_ofs;
obj->align.target = base;
obj->align.enabled = true;
gfx_obj_update_layout(obj);
gfx_obj_invalidate(obj);
GFX_LOGD(TAG, "Set object align_to: base=%p, type=%d, offset=(%d, %d)", base, align, x_ofs, y_ofs);
return ESP_OK;
}
esp_err_t gfx_obj_set_visible(gfx_obj_t *obj, bool visible)
{
GFX_RETURN_IF_NULL(obj, ESP_ERR_INVALID_ARG);
obj->state.is_visible = visible;
gfx_obj_invalidate(obj);
GFX_LOGD(TAG, "Set object visibility: %s", visible ? "visible" : "hidden");
return ESP_OK;
}
bool gfx_obj_get_visible(gfx_obj_t *obj)
{
GFX_RETURN_IF_NULL(obj, false);
return obj->state.is_visible;
}
void gfx_obj_update_layout(gfx_obj_t *obj)
{
GFX_RETURN_IF_NULL_VOID(obj);
if (obj->align.enabled) {
obj->state.layout_dirty = true;
}
}
/* Internal alignment helpers */
void gfx_obj_cal_aligned_pos(gfx_obj_t *obj, uint32_t parent_width, uint32_t parent_height, gfx_coord_t *x, gfx_coord_t *y)
{
GFX_RETURN_IF_NULL_VOID(obj);
GFX_RETURN_IF_NULL_VOID(x);
GFX_RETURN_IF_NULL_VOID(y);
if (!obj->align.enabled) {
*x = obj->geometry.x;
*y = obj->geometry.y;
return;
}
gfx_coord_t calculated_x = 0;
gfx_coord_t calculated_y = 0;
switch (obj->align.type) {
case GFX_ALIGN_TOP_LEFT:
calculated_x = obj->align.x_ofs;
calculated_y = obj->align.y_ofs;
break;
case GFX_ALIGN_TOP_MID:
calculated_x = ((gfx_coord_t)parent_width - obj->geometry.width) / 2 + obj->align.x_ofs;
calculated_y = obj->align.y_ofs;
break;
case GFX_ALIGN_TOP_RIGHT:
calculated_x = (gfx_coord_t)parent_width - obj->geometry.width + obj->align.x_ofs;
calculated_y = obj->align.y_ofs;
break;
case GFX_ALIGN_LEFT_MID:
calculated_x = obj->align.x_ofs;
calculated_y = ((gfx_coord_t)parent_height - obj->geometry.height) / 2 + obj->align.y_ofs;
break;
case GFX_ALIGN_CENTER:
calculated_x = ((gfx_coord_t)parent_width - obj->geometry.width) / 2 + obj->align.x_ofs;
calculated_y = ((gfx_coord_t)parent_height - obj->geometry.height) / 2 + obj->align.y_ofs;
break;
case GFX_ALIGN_RIGHT_MID:
calculated_x = (gfx_coord_t)parent_width - obj->geometry.width + obj->align.x_ofs;
calculated_y = ((gfx_coord_t)parent_height - obj->geometry.height) / 2 + obj->align.y_ofs;
break;
case GFX_ALIGN_BOTTOM_LEFT:
calculated_x = obj->align.x_ofs;
calculated_y = (gfx_coord_t)parent_height - obj->geometry.height + obj->align.y_ofs;
break;
case GFX_ALIGN_BOTTOM_MID:
calculated_x = ((gfx_coord_t)parent_width - obj->geometry.width) / 2 + obj->align.x_ofs;
calculated_y = (gfx_coord_t)parent_height - obj->geometry.height + obj->align.y_ofs;
break;
case GFX_ALIGN_BOTTOM_RIGHT:
calculated_x = (gfx_coord_t)parent_width - obj->geometry.width + obj->align.x_ofs;
calculated_y = (gfx_coord_t)parent_height - obj->geometry.height + obj->align.y_ofs;
break;
case GFX_ALIGN_OUT_TOP_LEFT:
calculated_x = obj->align.x_ofs;
calculated_y = -obj->geometry.height + obj->align.y_ofs;
break;
case GFX_ALIGN_OUT_TOP_MID:
calculated_x = ((gfx_coord_t)parent_width - obj->geometry.width) / 2 + obj->align.x_ofs;
calculated_y = -obj->geometry.height + obj->align.y_ofs;
break;
case GFX_ALIGN_OUT_TOP_RIGHT:
calculated_x = (gfx_coord_t)parent_width + obj->align.x_ofs;
calculated_y = -obj->geometry.height + obj->align.y_ofs;
break;
case GFX_ALIGN_OUT_LEFT_TOP:
calculated_x = -obj->geometry.width + obj->align.x_ofs;
calculated_y = obj->align.y_ofs;
break;
case GFX_ALIGN_OUT_LEFT_MID:
calculated_x = -obj->geometry.width + obj->align.x_ofs;
calculated_y = ((gfx_coord_t)parent_height - obj->geometry.height) / 2 + obj->align.y_ofs;
break;
case GFX_ALIGN_OUT_LEFT_BOTTOM:
calculated_x = -obj->geometry.width + obj->align.x_ofs;
calculated_y = (gfx_coord_t)parent_height + obj->align.y_ofs;
break;
case GFX_ALIGN_OUT_RIGHT_TOP:
calculated_x = (gfx_coord_t)parent_width + obj->align.x_ofs;
calculated_y = obj->align.y_ofs;
break;
case GFX_ALIGN_OUT_RIGHT_MID:
calculated_x = (gfx_coord_t)parent_width + obj->align.x_ofs;
calculated_y = ((gfx_coord_t)parent_height - obj->geometry.height) / 2 + obj->align.y_ofs;
break;
case GFX_ALIGN_OUT_RIGHT_BOTTOM:
calculated_x = (gfx_coord_t)parent_width + obj->align.x_ofs;
calculated_y = (gfx_coord_t)parent_height + obj->align.y_ofs;
break;
case GFX_ALIGN_OUT_BOTTOM_LEFT:
calculated_x = obj->align.x_ofs;
calculated_y = (gfx_coord_t)parent_height + obj->align.y_ofs;
break;
case GFX_ALIGN_OUT_BOTTOM_MID:
calculated_x = ((gfx_coord_t)parent_width - obj->geometry.width) / 2 + obj->align.x_ofs;
calculated_y = (gfx_coord_t)parent_height + obj->align.y_ofs;
break;
case GFX_ALIGN_OUT_BOTTOM_RIGHT:
calculated_x = (gfx_coord_t)parent_width + obj->align.x_ofs;
calculated_y = (gfx_coord_t)parent_height + obj->align.y_ofs;
break;
default:
GFX_LOGW(TAG, "Unknown alignment type: %d", obj->align.type);
calculated_x = obj->geometry.x;
calculated_y = obj->geometry.y;
break;
}
*x = calculated_x;
*y = calculated_y;
}
void gfx_obj_calc_pos_in_parent(gfx_obj_t *obj)
{
gfx_obj_calc_pos_in_parent_internal(obj, 0);
}
/* Generic getters */
esp_err_t gfx_obj_get_pos(gfx_obj_t *obj, gfx_coord_t *x, gfx_coord_t *y)
{
GFX_RETURN_IF_NULL(obj, ESP_ERR_INVALID_ARG);
GFX_RETURN_IF_NULL(x, ESP_ERR_INVALID_ARG);
GFX_RETURN_IF_NULL(y, ESP_ERR_INVALID_ARG);
*x = obj->geometry.x;
*y = obj->geometry.y;
return ESP_OK;
}
esp_err_t gfx_obj_get_size(gfx_obj_t *obj, uint16_t *w, uint16_t *h)
{
GFX_RETURN_IF_NULL(obj, ESP_ERR_INVALID_ARG);
GFX_RETURN_IF_NULL(w, ESP_ERR_INVALID_ARG);
GFX_RETURN_IF_NULL(h, ESP_ERR_INVALID_ARG);
*w = obj->geometry.width;
*h = obj->geometry.height;
return ESP_OK;
}
/*=====================
* Touch callback (application listener)
*====================*/
esp_err_t gfx_obj_set_touch_cb(gfx_obj_t *obj, gfx_obj_touch_cb_t cb, void *user_data)
{
GFX_RETURN_IF_NULL(obj, ESP_ERR_INVALID_ARG);
obj->user_touch_cb = cb;
obj->user_touch_data = user_data;
return ESP_OK;
}
uint32_t gfx_obj_get_trace_id(gfx_obj_t *obj)
{
GFX_RETURN_IF_NULL(obj, 0U);
return obj->trace.create_seq;
}
const char *gfx_obj_get_class_name(gfx_obj_t *obj)
{
GFX_RETURN_IF_NULL(obj, NULL);
return obj->trace.class_name;
}
const char *gfx_obj_get_trace_tag(gfx_obj_t *obj)
{
GFX_RETURN_IF_NULL(obj, NULL);
return obj->trace.create_tag;
}
/*=====================
* Other functions
*====================*/
esp_err_t gfx_obj_delete(gfx_obj_t *obj)
{
GFX_RETURN_IF_NULL(obj, ESP_ERR_INVALID_ARG);
if (GFX_NOT_NULL(obj->disp)) {
gfx_disp_remove_child(obj->disp, obj);
}
gfx_obj_detach_aligned_dependents(obj);
gfx_obj_invalidate(obj);
gfx_obj_notify_aligned_dependents(obj, 0);
/* Call object's delete function if available */
if (obj->vfunc.delete) {
obj->vfunc.delete(obj);
}
free(obj);
return ESP_OK;
}

View File

@ -0,0 +1,119 @@
/*
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "core/gfx_obj.h"
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* DEFINES
*********************/
#define DEFAULT_SCREEN_WIDTH 320
#define DEFAULT_SCREEN_HEIGHT 240
#define GFX_DRAW_CTX_DEST_PTR(ctx, x, y) \
((gfx_color_t *)((uint8_t *)(ctx)->buf + \
((y) - (ctx)->buf_area.y1) * (ctx)->stride * GFX_PIXEL_SIZE_16BPP + \
((x) - (ctx)->buf_area.x1) * GFX_PIXEL_SIZE_16BPP))
/**********************
* TYPEDEFS
**********************/
typedef struct gfx_draw_ctx {
void *buf; /**< Buffer start (chunk start or offset into full-frame) */
gfx_area_t buf_area; /**< Half-open screen rect [x1, x2) x [y1, y2); buf[0] maps to (buf_area.x1, buf_area.y1) */
gfx_area_t clip_area; /**< Half-open screen rect [x1, x2) x [y1, y2) for this draw pass */
int stride; /**< Row stride in pixels (chunk width or h_res) */
bool swap; /**< Color byte swap */
} gfx_draw_ctx_t;
typedef esp_err_t (*gfx_obj_draw_fn_t)(gfx_obj_t *obj, const gfx_draw_ctx_t *ctx);
typedef esp_err_t (*gfx_obj_delete_fn_t)(gfx_obj_t *obj);
typedef esp_err_t (*gfx_obj_update_fn_t)(gfx_obj_t *obj);
typedef void (*gfx_obj_touch_fn_t)(gfx_obj_t *obj, const void *event);
typedef struct gfx_widget_class {
uint8_t type; /**< Stable object type id */
const char *name; /**< Debug-friendly class name */
gfx_obj_draw_fn_t draw; /**< Draw callback */
gfx_obj_delete_fn_t delete; /**< Delete callback */
gfx_obj_update_fn_t update; /**< Update callback */
gfx_obj_touch_fn_t touch_event;/**< Touch callback */
} gfx_widget_class_t;
struct gfx_obj {
void *src; /**< Source data (image, label, etc.) */
int type; /**< Object type */
const gfx_widget_class_t *klass; /**< Registered class metadata */
gfx_disp_t *disp; /**< Display this object belongs to (from gfx_emote_add_disp) */
struct {
gfx_coord_t x; /**< X position */
gfx_coord_t y; /**< Y position */
uint16_t width; /**< Object width */
uint16_t height; /**< Object height */
} geometry;
struct {
uint8_t type; /**< Alignment type (see GFX_ALIGN_* constants) */
gfx_coord_t x_ofs; /**< X offset for alignment */
gfx_coord_t y_ofs; /**< Y offset for alignment */
gfx_obj_t *target; /**< Reference object for align_to; NULL means align to display */
bool enabled; /**< Whether to use alignment instead of absolute position */
} align;
struct {
bool is_visible: 1; /**< Object visibility */
bool layout_dirty: 1; /**< Whether layout needs to be recalculated before rendering */
bool dirty: 1; /**< Whether the object is dirty */
} state;
struct {
gfx_obj_draw_fn_t draw; /**< Draw function pointer */
gfx_obj_delete_fn_t delete; /**< Delete function pointer */
gfx_obj_update_fn_t update; /**< Update function pointer */
gfx_obj_touch_fn_t touch_event; /**< Touch event (optional, NULL = no handler) */
} vfunc;
/** Application touch callback (from gfx_obj_set_touch_cb) */
gfx_obj_touch_cb_t user_touch_cb;
void *user_touch_data;
struct {
uint32_t create_seq; /**< Monotonic object creation sequence */
const char *class_name; /**< Widget class name */
const char *create_tag; /**< Creation annotation tag */
} trace;
};
typedef struct gfx_obj_child_t {
void *src;
struct gfx_obj_child_t *next;
} gfx_obj_child_t;
/**********************
* INTERNAL API
**********************/
esp_err_t gfx_widget_class_register(const gfx_widget_class_t *klass);
const gfx_widget_class_t *gfx_widget_class_get(uint8_t type);
esp_err_t gfx_obj_init_class_instance(gfx_obj_t *obj, gfx_disp_t *disp, const gfx_widget_class_t *klass, void *src);
esp_err_t gfx_obj_create_class_instance(gfx_disp_t *disp, const gfx_widget_class_t *klass,
void *src, uint16_t width, uint16_t height,
const char *create_tag, gfx_obj_t **out_obj);
void gfx_obj_cal_aligned_pos(gfx_obj_t *obj, uint32_t parent_width, uint32_t parent_height, gfx_coord_t *x, gfx_coord_t *y);
void gfx_obj_calc_pos_in_parent(gfx_obj_t *obj);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,113 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
#include "esp_check.h"
#define GFX_LOG_MODULE GFX_LOG_MODULE_OBJ
#include "common/gfx_log_priv.h"
#include "core/display/gfx_disp_priv.h"
#include "core/display/gfx_refr_priv.h"
#include "core/object/gfx_obj_priv.h"
static const char *TAG = "widget_class";
static const gfx_widget_class_t *s_widget_classes[UINT8_MAX + 1U];
static uint32_t s_obj_create_seq = 0U;
esp_err_t gfx_widget_class_register(const gfx_widget_class_t *klass)
{
const gfx_widget_class_t *existing;
ESP_RETURN_ON_FALSE(klass != NULL, ESP_ERR_INVALID_ARG, TAG, "class is NULL");
existing = s_widget_classes[klass->type];
if (existing == NULL) {
s_widget_classes[klass->type] = klass;
return ESP_OK;
}
ESP_RETURN_ON_FALSE(existing == klass, ESP_ERR_INVALID_STATE, TAG,
"class type %u already registered by %s",
(unsigned int)klass->type,
existing->name ? existing->name : "unknown");
return ESP_OK;
}
const gfx_widget_class_t *gfx_widget_class_get(uint8_t type)
{
return s_widget_classes[type];
}
esp_err_t gfx_obj_init_class_instance(gfx_obj_t *obj, gfx_disp_t *disp, const gfx_widget_class_t *klass, void *src)
{
ESP_RETURN_ON_FALSE(obj != NULL, ESP_ERR_INVALID_ARG, TAG, "object is NULL");
ESP_RETURN_ON_FALSE(disp != NULL, ESP_ERR_INVALID_ARG, TAG, "display is NULL");
ESP_RETURN_ON_FALSE(klass != NULL, ESP_ERR_INVALID_ARG, TAG, "class is NULL");
ESP_RETURN_ON_ERROR(gfx_widget_class_register(klass), TAG, "register class failed");
memset(obj, 0, sizeof(*obj));
obj->type = klass->type;
obj->klass = gfx_widget_class_get(klass->type);
obj->disp = disp;
obj->src = src;
obj->state.is_visible = true;
obj->vfunc.draw = obj->klass->draw;
obj->vfunc.delete = obj->klass->delete;
obj->vfunc.update = obj->klass->update;
obj->vfunc.touch_event = obj->klass->touch_event;
obj->trace.create_seq = ++s_obj_create_seq;
obj->trace.class_name = (obj->klass->name != NULL) ? obj->klass->name : "unknown";
obj->trace.create_tag = obj->trace.class_name;
gfx_obj_invalidate(obj);
return ESP_OK;
}
esp_err_t gfx_obj_create_class_instance(gfx_disp_t *disp, const gfx_widget_class_t *klass,
void *src, uint16_t width, uint16_t height,
const char *create_tag, gfx_obj_t **out_obj)
{
gfx_obj_t *obj;
esp_err_t ret;
ESP_RETURN_ON_FALSE(disp != NULL, ESP_ERR_INVALID_ARG, TAG, "display is NULL");
ESP_RETURN_ON_FALSE(klass != NULL, ESP_ERR_INVALID_ARG, TAG, "class is NULL");
ESP_RETURN_ON_FALSE(out_obj != NULL, ESP_ERR_INVALID_ARG, TAG, "output object is NULL");
*out_obj = NULL;
obj = calloc(1, sizeof(*obj));
ESP_RETURN_ON_FALSE(obj != NULL, ESP_ERR_NO_MEM, TAG, "no memory for object");
ret = gfx_obj_init_class_instance(obj, disp, klass, src);
if (ret != ESP_OK) {
free(obj);
return ret;
}
obj->geometry.width = width;
obj->geometry.height = height;
if (create_tag != NULL) {
obj->trace.create_tag = create_tag;
}
ret = gfx_disp_add_child(disp, obj);
if (ret != ESP_OK) {
free(obj);
return ret;
}
GFX_LOGD(TAG, "created obj#%" PRIu32 " class=%s tag=%s",
obj->trace.create_seq,
obj->trace.class_name ? obj->trace.class_name : "unknown",
obj->trace.create_tag ? obj->trace.create_tag : "unknown");
*out_obj = obj;
return ESP_OK;
}

View File

@ -0,0 +1,332 @@
/*
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*********************
* INCLUDES
*********************/
#include <stdbool.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_timer.h"
#include "esp_check.h"
#define GFX_LOG_MODULE GFX_LOG_MODULE_CORE
#include "common/gfx_log_priv.h"
#include "core/gfx_obj.h"
#include "core/display/gfx_refr_priv.h"
#include "core/display/gfx_render_priv.h"
#include "core/object/gfx_obj_priv.h"
#include "core/runtime/gfx_timer_priv.h"
#include "core/runtime/gfx_touch_priv.h"
#include "widget/img/gfx_img_dec_priv.h"
#include "widget/font/gfx_font_priv.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC VARIABLES
**********************/
static const char *TAG = "core";
/**********************
* STATIC PROTOTYPES
**********************/
static void gfx_render_loop_task(void *arg);
static uint32_t gfx_cal_task_delay(uint32_t timer_delay);
static void gfx_do_refr_now_impl(gfx_core_context_t *ctx);
static inline TickType_t gfx_block_ticks(uint32_t ms);
static void gfx_wait_for_work(gfx_core_context_t *ctx, uint32_t next_sleep_ms, EventBits_t *out_triggered);
/**********************
* STATIC FUNCTIONS
**********************/
/** Convert ms to block time in ticks; minimum 1 tick. */
static inline TickType_t gfx_block_ticks(uint32_t ms)
{
TickType_t t = pdMS_TO_TICKS(ms);
return (t >= 1) ? t : 1;
}
/**
* Wait for render work or lifecycle exit. Checks NEED_DELETE first (no block);
* if set, signals DELETE_DONE and deletes the task (never returns).
* Otherwise waits on render_events for up to next_sleep_ms and returns evt_invalidate bits.
*/
static void gfx_wait_for_work(gfx_core_context_t *ctx, uint32_t next_sleep_ms, EventBits_t *out_triggered)
{
EventBits_t life = xEventGroupWaitBits(ctx->sync.lifecycle_events, NEED_DELETE,
pdTRUE, pdFALSE, 0);
if (life & NEED_DELETE) {
xEventGroupSetBits(ctx->sync.lifecycle_events, DELETE_DONE);
vTaskDeleteWithCaps(NULL);
/* never returns */
}
if (ctx->sync.render_events != NULL) {
*out_triggered = xEventGroupWaitBits(ctx->sync.render_events, GFX_EVENT_ALL,
pdTRUE, pdFALSE, gfx_block_ticks(next_sleep_ms));
} else {
vTaskDelay(gfx_block_ticks(next_sleep_ms));
*out_triggered = 0;
}
}
static uint32_t gfx_cal_task_delay(uint32_t timer_delay)
{
uint32_t min_delay_ms = (1000 / configTICK_RATE_HZ) + 1; // At least one tick + 1ms
if (timer_delay == ANIM_NO_TIMER_READY) {
return (min_delay_ms > 5) ? min_delay_ms : 5;
} else {
return (timer_delay < min_delay_ms) ? min_delay_ms : timer_delay;
}
}
static void gfx_render_loop_task(void *arg)
{
gfx_core_context_t *ctx = (gfx_core_context_t *)arg;
SemaphoreHandle_t mutex = ctx->sync.render_mutex;
uint32_t next_sleep_ms = GFX_RENDER_TASK_IDLE_SLEEP_MS;
/**
* If this task runs too early, add_disp() can fire the first frame before the
* caller / external code is ready, which can lead to a deadlock. Delay so the
* rest of the system can finish setup first.
*/
vTaskDelay(pdMS_TO_TICKS(GFX_RENDER_TASK_IDLE_SLEEP_MS));
for (;;) {
EventBits_t evt_invalidate;
gfx_wait_for_work(ctx, next_sleep_ms, &evt_invalidate);
bool locked = (mutex != NULL && xSemaphoreTakeRecursive(mutex, portMAX_DELAY) == pdTRUE);
if (!locked) {
next_sleep_ms = 1;
vTaskDelay(pdMS_TO_TICKS(1));
continue;
}
bool evt_refr;
uint32_t time_until_next = gfx_timer_handler(&ctx->timer_mgr, &evt_refr);
bool need_refr = (evt_invalidate != 0) || evt_refr;
if (need_refr) {
gfx_do_refr_now_impl(ctx);
}
next_sleep_ms = gfx_cal_task_delay(time_until_next);
xSemaphoreGiveRecursive(mutex);
vTaskDelay(pdMS_TO_TICKS(1));
}
}
static void gfx_do_refr_now_impl(gfx_core_context_t *ctx)
{
if (ctx->disp != NULL) {
gfx_render_handler(ctx);
}
}
/**********************
* PUBLIC FUNCTIONS
**********************/
gfx_handle_t gfx_emote_init(const gfx_core_config_t *cfg)
{
esp_err_t ret = ESP_OK;
gfx_core_context_t *disp_ctx = NULL;
BaseType_t task_ret = pdFAIL;
bool lifecycle_events_created = false;
bool mutex_created = false;
bool decoder_inited = false;
#ifdef CONFIG_GFX_FONT_FREETYPE_SUPPORT
bool font_lib_created = false;
#endif
ESP_GOTO_ON_FALSE(cfg, ESP_ERR_INVALID_ARG, err, TAG, "Invalid configuration");
disp_ctx = malloc(sizeof(gfx_core_context_t));
ESP_GOTO_ON_FALSE(disp_ctx, ESP_ERR_NO_MEM, err, TAG, "Failed to allocate player context");
memset(disp_ctx, 0, sizeof(gfx_core_context_t));
disp_ctx->sync.lifecycle_events = xEventGroupCreate();
ESP_GOTO_ON_FALSE(disp_ctx->sync.lifecycle_events, ESP_ERR_NO_MEM, err, TAG, "Failed to create event group");
lifecycle_events_created = true;
disp_ctx->sync.render_events = xEventGroupCreate();
ESP_GOTO_ON_FALSE(disp_ctx->sync.render_events, ESP_ERR_NO_MEM, err, TAG, "Failed to create render event group");
disp_ctx->sync.render_mutex = xSemaphoreCreateRecursiveMutex();
ESP_GOTO_ON_FALSE(disp_ctx->sync.render_mutex, ESP_ERR_NO_MEM, err, TAG, "Failed to create recursive render mutex");
mutex_created = true;
#ifdef CONFIG_GFX_FONT_FREETYPE_SUPPORT
ret = gfx_ft_lib_create();
ESP_GOTO_ON_ERROR(ret, err, TAG, "Failed to create font library");
font_lib_created = true;
#endif
gfx_timer_mgr_init(&disp_ctx->timer_mgr, cfg->fps);
ret = gfx_image_decoder_init();
ESP_GOTO_ON_ERROR(ret, err, TAG, "Failed to initialize image decoder");
decoder_inited = true;
const uint32_t stack_caps = cfg->task.task_stack_caps ? cfg->task.task_stack_caps : (MALLOC_CAP_INTERNAL | MALLOC_CAP_DEFAULT);
if (cfg->task.task_affinity < 0) {
task_ret = xTaskCreateWithCaps(gfx_render_loop_task, "gfx_render", cfg->task.task_stack,
disp_ctx, cfg->task.task_priority, NULL, stack_caps);
} else {
task_ret = xTaskCreatePinnedToCoreWithCaps(gfx_render_loop_task, "gfx_render", cfg->task.task_stack,
disp_ctx, cfg->task.task_priority, NULL, cfg->task.task_affinity, stack_caps);
}
ESP_GOTO_ON_FALSE(task_ret == pdPASS, ESP_ERR_NO_MEM, err, TAG, "Failed to create render task");
return (gfx_handle_t)disp_ctx;
err:
if (decoder_inited) {
gfx_image_decoder_deinit();
}
#ifdef CONFIG_GFX_FONT_FREETYPE_SUPPORT
if (font_lib_created) {
gfx_ft_lib_cleanup();
}
#endif
if (mutex_created) {
vSemaphoreDelete(disp_ctx->sync.render_mutex);
}
if (disp_ctx->sync.render_events) {
vEventGroupDelete(disp_ctx->sync.render_events);
disp_ctx->sync.render_events = NULL;
}
if (lifecycle_events_created) {
vEventGroupDelete(disp_ctx->sync.lifecycle_events);
}
free(disp_ctx);
return NULL;
}
void gfx_emote_deinit(gfx_handle_t handle)
{
gfx_core_context_t *ctx = (gfx_core_context_t *)handle;
if (ctx == NULL) {
GFX_LOGE(TAG, "deinit graphics: context is NULL");
return;
}
xEventGroupSetBits(ctx->sync.lifecycle_events, NEED_DELETE);
xEventGroupWaitBits(ctx->sync.lifecycle_events, DELETE_DONE, pdTRUE, pdFALSE, portMAX_DELAY);
while (ctx->disp != NULL) {
gfx_disp_t *d = ctx->disp;
gfx_disp_delete_children(d);
gfx_disp_del(d);
free(d);
}
while (ctx->touch != NULL) {
gfx_touch_t *t = ctx->touch;
gfx_touch_del(t);
free(t);
}
gfx_timer_mgr_deinit(&ctx->timer_mgr);
#ifdef CONFIG_GFX_FONT_FREETYPE_SUPPORT
gfx_ft_lib_cleanup();
#endif
if (ctx->sync.render_mutex) {
vSemaphoreDelete(ctx->sync.render_mutex);
ctx->sync.render_mutex = NULL;
}
if (ctx->sync.lifecycle_events) {
vEventGroupDelete(ctx->sync.lifecycle_events);
ctx->sync.lifecycle_events = NULL;
}
if (ctx->sync.render_events) {
vEventGroupDelete(ctx->sync.render_events);
ctx->sync.render_events = NULL;
}
gfx_image_decoder_deinit();
free(ctx);
}
esp_err_t gfx_refr_now(gfx_handle_t handle)
{
gfx_core_context_t *ctx = (gfx_core_context_t *)handle;
SemaphoreHandle_t mutex = ctx ? ctx->sync.render_mutex : NULL;
if (ctx == NULL || mutex == NULL) {
GFX_LOGE(TAG, "refresh now: context or mutex is NULL");
return ESP_ERR_INVALID_ARG;
}
if (xSemaphoreTakeRecursive(mutex, portMAX_DELAY) != pdTRUE) {
GFX_LOGE(TAG, "refresh now: acquire mutex failed");
return ESP_ERR_TIMEOUT;
}
gfx_do_refr_now_impl(ctx);
if (xSemaphoreGiveRecursive(mutex) != pdTRUE) {
GFX_LOGE(TAG, "refresh now: release mutex failed");
return ESP_ERR_INVALID_STATE;
}
return ESP_OK;
}
esp_err_t gfx_emote_lock(gfx_handle_t handle)
{
gfx_core_context_t *ctx = (gfx_core_context_t *)handle;
SemaphoreHandle_t mutex = ctx ? ctx->sync.render_mutex : NULL;
if (ctx == NULL || mutex == NULL) {
GFX_LOGE(TAG, "lock graphics: context or mutex is NULL");
return ESP_ERR_INVALID_ARG;
}
if (xSemaphoreTakeRecursive(mutex, portMAX_DELAY) != pdTRUE) {
GFX_LOGE(TAG, "lock graphics: acquire mutex failed");
return ESP_ERR_TIMEOUT;
}
return ESP_OK;
}
esp_err_t gfx_emote_unlock(gfx_handle_t handle)
{
gfx_core_context_t *ctx = (gfx_core_context_t *)handle;
SemaphoreHandle_t mutex = ctx ? ctx->sync.render_mutex : NULL;
if (ctx == NULL || mutex == NULL) {
GFX_LOGE(TAG, "unlock graphics: context or mutex is NULL");
return ESP_ERR_INVALID_ARG;
}
if (xSemaphoreGiveRecursive(mutex) != pdTRUE) {
GFX_LOGE(TAG, "unlock graphics: release mutex failed");
return ESP_ERR_INVALID_STATE;
}
return ESP_OK;
}

View File

@ -0,0 +1,58 @@
/*
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/event_groups.h"
#include "core/gfx_core.h"
#include "core/gfx_touch.h"
#include "core/display/gfx_disp_priv.h"
#include "core/object/gfx_obj_priv.h"
#include "core/runtime/gfx_timer_priv.h"
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* DEFINES
*********************/
#define NEED_DELETE BIT0
#define DELETE_DONE BIT1
#define WAIT_FLUSH_DONE BIT2
#define GFX_EVENT_INVALIDATE BIT0
#define GFX_EVENT_ALL 0xFF
#define ANIM_NO_TIMER_READY 0xFFFFFFFF
#define GFX_RENDER_TASK_IDLE_SLEEP_MS 100
/*********************
* TYPEDEFS
*********************/
typedef struct gfx_core_context {
struct {
SemaphoreHandle_t render_mutex; /**< Recursive mutex for render/touch */
EventGroupHandle_t lifecycle_events; /**< NEED_DELETE / DELETE_DONE / WAIT_FLUSH_DONE */
EventGroupHandle_t render_events; /**< GFX_EVENT_INVALIDATE etc. - wake render task */
} sync;
gfx_timer_mgr_t timer_mgr; /**< Timer manager (see gfx_timer_priv.h) */
gfx_disp_t *disp; /**< Display list (one per screen, malloc'd) */
gfx_touch_t *touch; /**< Touch list (multiple touch devices, malloc'd) */
} gfx_core_context_t;
/*********************
* INTERNAL API
*********************/
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,322 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*********************
* INCLUDES
*********************/
#include "esp_timer.h"
#include "esp_err.h"
#include "esp_log.h"
#define GFX_LOG_MODULE GFX_LOG_MODULE_TIMER
#include "common/gfx_log_priv.h"
#include "core/runtime/gfx_core_priv.h"
/*********************
* DEFINES
*********************/
#define GFX_NO_TIMER_READY 0xFFFFFFFF
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC VARIABLES
**********************/
static const char *TAG = "timer";
/**********************
* STATIC PROTOTYPES
**********************/
/**********************
* STATIC FUNCTIONS
**********************/
/**********************
* PUBLIC FUNCTIONS
**********************/
uint32_t gfx_timer_tick_get(void)
{
return (uint32_t)(esp_timer_get_time() / 1000); // Convert microseconds to milliseconds
}
uint32_t gfx_timer_tick_elaps(uint32_t prev_tick)
{
uint32_t act_time = gfx_timer_tick_get();
/*If there is no overflow in sys_time simple subtract*/
if (act_time >= prev_tick) {
prev_tick = act_time - prev_tick;
} else {
prev_tick = UINT32_MAX - prev_tick + 1;
prev_tick += act_time;
}
return prev_tick;
}
bool gfx_timer_exec(gfx_timer_t *timer)
{
if (timer == NULL || timer->paused) {
GFX_LOGD(TAG, "run timer callback: timer is NULL or paused");
return false;
}
// Don't execute if repeat_count is 0 (timer completed)
if (timer->repeat_count == 0) {
return false;
}
uint32_t time_elapsed = gfx_timer_tick_elaps(timer->last_run);
if (time_elapsed >= timer->period) {
timer->last_run = gfx_timer_tick_get() - (time_elapsed % timer->period);
if (timer->timer_cb) {
timer->timer_cb(timer->user_data);
}
if (timer->repeat_count > 0) {
timer->repeat_count--;
}
return true;
}
return false;
}
uint32_t gfx_timer_handler(gfx_timer_mgr_t *timer_mgr, bool *out_should_render)
{
/* Step 1: execute timers and find the minimum remaining time */
uint32_t min_timer_remaining_ms = GFX_NO_TIMER_READY;
gfx_timer_t *timer_node = timer_mgr->timer_list;
gfx_timer_t *next_timer = NULL;
while (timer_node != NULL) {
next_timer = timer_node->next;
gfx_timer_exec(timer_node);
if (!timer_node->paused && timer_node->repeat_count != 0) {
uint32_t timer_elapsed_ms = gfx_timer_tick_elaps(timer_node->last_run);
uint32_t timer_remaining_ms = (timer_elapsed_ms >= timer_node->period)
? 0
: (timer_node->period - timer_elapsed_ms);
if (timer_remaining_ms < min_timer_remaining_ms) {
min_timer_remaining_ms = timer_remaining_ms;
}
}
timer_node = next_timer;
}
/* Step 2: update FPS period and render due state */
uint32_t fps_period_ms = (timer_mgr->fps > 0) ? (1000 / timer_mgr->fps) : 30;
uint32_t fps_elapsed_ms;
uint32_t fps_remaining_ms;
bool render_due;
if (timer_mgr->last_tick == 0) {
render_due = true;
timer_mgr->last_tick = gfx_timer_tick_get();
fps_remaining_ms = fps_period_ms;
} else {
fps_elapsed_ms = gfx_timer_tick_elaps(timer_mgr->last_tick);
fps_remaining_ms = (fps_elapsed_ms >= fps_period_ms) ? 0 : (fps_period_ms - fps_elapsed_ms);
render_due = (fps_remaining_ms == 0);
if (render_due) {
timer_mgr->last_tick = gfx_timer_tick_get();
}
}
/* Step 3: calculate the next task delay */
uint32_t task_delay_ms;
if (min_timer_remaining_ms == GFX_NO_TIMER_READY) {
task_delay_ms = (fps_remaining_ms > 0) ? fps_remaining_ms : 1;
} else {
task_delay_ms = (min_timer_remaining_ms < fps_remaining_ms)
? min_timer_remaining_ms
: fps_remaining_ms;
if (task_delay_ms == 0) {
task_delay_ms = 1;
}
}
timer_mgr->time_until_next = task_delay_ms;
if (out_should_render != NULL) {
*out_should_render = render_due;
}
return task_delay_ms;
}
gfx_timer_handle_t gfx_timer_create(void *handle, gfx_timer_cb_t timer_cb, uint32_t period, void *user_data)
{
if (handle == NULL || timer_cb == NULL) {
return NULL;
}
gfx_core_context_t *ctx = (gfx_core_context_t *)handle;
gfx_timer_mgr_t *timer_mgr = &ctx->timer_mgr;
gfx_timer_t *new_timer = (gfx_timer_t *)malloc(sizeof(gfx_timer_t));
if (new_timer == NULL) {
GFX_LOGE(TAG, "create timer: allocate timer failed");
return NULL;
}
new_timer->period = period;
new_timer->timer_cb = timer_cb;
new_timer->user_data = user_data;
new_timer->repeat_count = -1;
new_timer->paused = false;
new_timer->last_run = gfx_timer_tick_get();
new_timer->next = NULL;
if (timer_mgr->timer_list == NULL) {
timer_mgr->timer_list = new_timer;
} else {
gfx_timer_t *current_timer = timer_mgr->timer_list;
while (current_timer->next != NULL) {
current_timer = current_timer->next;
}
current_timer->next = new_timer;
}
return (gfx_timer_handle_t)new_timer;
}
void gfx_timer_delete(void *handle, gfx_timer_handle_t timer_handle)
{
if (handle == NULL || timer_handle == NULL) {
return;
}
gfx_timer_t *timer = (gfx_timer_t *)timer_handle;
gfx_core_context_t *ctx = (gfx_core_context_t *)handle;
gfx_timer_mgr_t *timer_mgr = &ctx->timer_mgr;
gfx_timer_t *current_timer = timer_mgr->timer_list;
gfx_timer_t *prev_timer = NULL;
while (current_timer != NULL && current_timer != timer) {
prev_timer = current_timer;
current_timer = current_timer->next;
}
if (current_timer == timer) {
if (prev_timer == NULL) {
timer_mgr->timer_list = timer->next;
} else {
prev_timer->next = timer->next;
}
free(timer);
GFX_LOGD(TAG, "delete timer: timer deleted");
}
}
void gfx_timer_pause(gfx_timer_handle_t timer_handle)
{
if (timer_handle != NULL) {
gfx_timer_t *timer = (gfx_timer_t *)timer_handle;
timer->paused = true;
}
}
void gfx_timer_resume(gfx_timer_handle_t timer_handle)
{
if (timer_handle != NULL) {
gfx_timer_t *timer = (gfx_timer_t *)timer_handle;
timer->paused = false;
timer->last_run = gfx_timer_tick_get();
if (timer->repeat_count == 0) {
timer->repeat_count = -1;
}
}
}
void gfx_timer_set_repeat_count(gfx_timer_handle_t timer_handle, int32_t repeat_count)
{
if (timer_handle != NULL) {
gfx_timer_t *timer = (gfx_timer_t *)timer_handle;
timer->repeat_count = repeat_count;
}
}
void gfx_timer_set_period(gfx_timer_handle_t timer_handle, uint32_t period)
{
if (timer_handle != NULL) {
gfx_timer_t *timer = (gfx_timer_t *)timer_handle;
timer->period = period;
}
}
void gfx_timer_reset(gfx_timer_handle_t timer_handle)
{
if (timer_handle != NULL) {
gfx_timer_t *timer = (gfx_timer_t *)timer_handle;
timer->last_run = gfx_timer_tick_get();
}
}
bool gfx_timer_is_running(gfx_timer_handle_t timer_handle)
{
if (timer_handle != NULL) {
gfx_timer_t *timer = (gfx_timer_t *)timer_handle;
return !timer->paused;
}
return false;
}
void gfx_timer_mgr_init(gfx_timer_mgr_t *timer_mgr, uint32_t fps)
{
if (timer_mgr != NULL) {
timer_mgr->timer_list = NULL;
timer_mgr->time_until_next = GFX_NO_TIMER_READY;
timer_mgr->last_tick = 0;
timer_mgr->fps = fps;
timer_mgr->actual_fps = 0;
GFX_LOGI(TAG, "init timer manager: fps=%"PRIu32" period=%"PRIu32" ms", fps, (fps > 0) ? (1000 / fps) : 30);
}
}
void gfx_timer_mgr_deinit(gfx_timer_mgr_t *timer_mgr)
{
if (timer_mgr == NULL) {
return;
}
gfx_timer_t *timer_node = timer_mgr->timer_list;
while (timer_node != NULL) {
gfx_timer_t *next_timer = timer_node->next;
free(timer_node);
timer_node = next_timer;
}
timer_mgr->timer_list = NULL;
}
uint32_t gfx_timer_get_actual_fps(void *handle)
{
if (handle == NULL) {
return 0;
}
gfx_core_context_t *ctx = (gfx_core_context_t *)handle;
gfx_timer_mgr_t *timer_mgr = &ctx->timer_mgr;
return timer_mgr->actual_fps;
}

View File

@ -0,0 +1,100 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "core/gfx_timer.h"
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/* Timer handle type for external use */
typedef void *gfx_timer_handle_t;
/* Timer structure (internal use) */
typedef struct gfx_timer_s {
uint32_t period;
uint32_t last_run;
gfx_timer_cb_t timer_cb;
void *user_data;
int32_t repeat_count;
bool paused;
struct gfx_timer_s *next;
} gfx_timer_t;
/* Timer manager structure (internal use) */
typedef struct {
gfx_timer_t *timer_list;
uint32_t time_until_next;
uint32_t last_tick;
uint32_t fps; ///< Target FPS for timer scheduling
uint32_t actual_fps; ///< Actual measured FPS
/* FPS statistics */
uint32_t fps_last_report_tick;
uint32_t fps_report_interval_ms;
} gfx_timer_mgr_t;
/**********************
* GLOBAL PROTOTYPES
**********************/
/*=====================
* Internal timer functions
*====================*/
/**
* @brief Get current system tick (internal)
* @return Current tick value in milliseconds
*/
uint32_t gfx_timer_tick_get(void);
/**
* @brief Calculate elapsed time since previous tick (internal)
* @param prev_tick Previous tick value
* @return Elapsed time in milliseconds
*/
uint32_t gfx_timer_tick_elaps(uint32_t prev_tick);
/**
* @brief Execute a timer
* @param timer Timer to execute
* @return true if timer was executed, false otherwise
*/
bool gfx_timer_exec(gfx_timer_t *timer);
/**
* @brief Handle timer manager operations
* @param timer_mgr Timer manager
* @param out_should_render If non-NULL, set to true when a render is due this frame (FPS interval elapsed)
* @return Time in ms until next timer or FPS tick (for task sleep)
*/
uint32_t gfx_timer_handler(gfx_timer_mgr_t *timer_mgr, bool *out_should_render);
/**
* @brief Initialize timer manager
* @param timer_mgr Timer manager to initialize
* @param fps Target FPS for timer scheduling
*/
void gfx_timer_mgr_init(gfx_timer_mgr_t *timer_mgr, uint32_t fps);
/**
* @brief Deinitialize timer manager
* @param timer_mgr Timer manager to deinitialize
*/
void gfx_timer_mgr_deinit(gfx_timer_mgr_t *timer_mgr);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,62 @@
/*
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "driver/gpio.h"
#include "esp_err.h"
#include "esp_lcd_touch.h"
#include "core/gfx_touch.h"
#include "core/runtime/gfx_timer_priv.h"
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* TYPEDEFS
*********************/
struct gfx_core_context;
struct gfx_obj;
/*********************
* INTERNAL STRUCTS
*********************/
/** Touch node (one per device); list chained by next; public API uses opaque gfx_touch_t */
struct gfx_touch {
struct gfx_touch *next;
struct gfx_core_context *ctx;
esp_lcd_touch_handle_t handle;
gfx_disp_t *disp;
gfx_timer_handle_t poll_timer;
gfx_touch_event_cb_t event_cb;
void *user_data;
uint32_t poll_ms;
bool pressed;
uint16_t last_x;
uint16_t last_y;
uint16_t last_strength;
uint8_t last_id;
/** Object that received PRESS; gets MOVE/RELEASE for same track until RELEASE (for drag) */
struct gfx_obj *pressed_obj;
uint8_t pressed_id;
gpio_num_t int_gpio_num;
bool irq_enabled;
volatile bool irq_pending;
void *isr_ctx;
};
/*********************
* INTERNAL API
*********************/
esp_err_t gfx_touch_start(gfx_touch_t *touch, const gfx_touch_config_t *cfg);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,956 @@
/*
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*********************
* INCLUDES
*********************/
#include <string.h>
#include "esp_err.h"
#include "esp_log.h"
#include "esp_check.h"
#define GFX_LOG_MODULE GFX_LOG_MODULE_EAF_DEC
#include "common/gfx_log_priv.h"
#include "gfx_eaf_dec.h"
#if CONFIG_GFX_EAF_JPEG_DECODE_SUPPORT
#include "esp_jpeg_dec.h"
#endif
#ifdef CONFIG_GFX_EAF_HEATSHRINK_SUPPORT
#include "heatshrink_decoder.h"
#endif // CONFIG_GFX_EAF_HEATSHRINK_SUPPORT
/*********************
* DEFINES
*********************/
#define EAF_FRAME_VERSION_OFFSET (3)
#define EAF_FRAME_BIT_DEPTH_OFFSET (9)
#define EAF_FRAME_WIDTH_OFFSET (10)
#define EAF_FRAME_HEIGHT_OFFSET (12)
#define EAF_FRAME_BLOCKS_OFFSET (14)
#define EAF_FRAME_BLOCK_HEIGHT_OFFSET (16)
#define EAF_FRAME_BLOCK_LEN_TABLE_OFFSET (18)
#define EAF_FRAME_BLOCK_LEN_SIZE (4)
#define EAF_FRAME_PALETTE_ENTRY_SIZE (4)
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC VARIABLES
**********************/
static const char *TAG = "eaf_dec";
static eaf_dec_block_decoder_cb_t s_eaf_decoders[EAF_DEC_ENCODING_MAX] = {0};
/**********************
* STATIC PROTOTYPES
**********************/
static uint32_t dec_calculate_checksum(const uint8_t *data, uint32_t length);
static eaf_dec_huffman_node_t *huffman_node_create(void);
static void huffman_tree_free(eaf_dec_huffman_node_t *node);
static esp_err_t huffman_decode_data(const uint8_t *in_data, size_t in_size,
const uint8_t *dict_data, size_t dict_len,
uint8_t *out_data, size_t *out_size);
/**********************
* STATIC FUNCTIONS
**********************/
static uint32_t dec_calculate_checksum(const uint8_t *data, uint32_t length)
{
uint32_t checksum = 0;
for (uint32_t i = 0; i < length; i++) {
checksum += data[i];
}
return checksum;
}
static eaf_dec_huffman_node_t *huffman_node_create(void)
{
eaf_dec_huffman_node_t *node = (eaf_dec_huffman_node_t *)calloc(1, sizeof(eaf_dec_huffman_node_t));
return node;
}
static void huffman_tree_free(eaf_dec_huffman_node_t *node)
{
if (!node) {
return;
}
huffman_tree_free(node->left);
huffman_tree_free(node->right);
free(node);
}
static esp_err_t huffman_decode_data(const uint8_t *in_data, size_t in_size,
const uint8_t *dict_data, size_t dict_len,
uint8_t *out_data, size_t *out_size)
{
if (!in_data || !dict_data || in_size == 0 || dict_len == 0) {
*out_size = 0;
return ESP_OK;
}
uint8_t padding_bits = dict_data[0];
size_t dict_pos = 1;
eaf_dec_huffman_node_t *root = huffman_node_create();
eaf_dec_huffman_node_t *current_node = NULL;
while (dict_pos < dict_len) {
uint8_t symbol = dict_data[dict_pos++];
uint8_t code_len = dict_data[dict_pos++];
size_t code_byte_len = (code_len + 7) / 8;
uint64_t code = 0;
for (size_t i = 0; i < code_byte_len; ++i) {
code = (code << 8) | dict_data[dict_pos++];
}
current_node = root;
for (int bit_pos = code_len - 1; bit_pos >= 0; --bit_pos) {
int bit_val = (code >> bit_pos) & 1;
if (bit_val == 0) {
if (!current_node->left) {
current_node->left = huffman_node_create();
}
current_node = current_node->left;
} else {
if (!current_node->right) {
current_node->right = huffman_node_create();
}
current_node = current_node->right;
}
}
current_node->is_leaf = 1;
current_node->symbol = symbol;
}
size_t total_bits = in_size * 8;
if (padding_bits > 0) {
total_bits -= padding_bits;
}
current_node = root;
size_t out_pos = 0;
for (size_t bit_index = 0; bit_index < total_bits; bit_index++) {
size_t byte_idx = bit_index / 8;
int bit_offset = 7 - (bit_index % 8);
int bit_val = (in_data[byte_idx] >> bit_offset) & 1;
if (bit_val == 0) {
current_node = current_node->left;
} else {
current_node = current_node->right;
}
if (current_node == NULL) {
GFX_LOGE(TAG, "Invalid Huffman path at bit %d", (int)bit_index);
break;
}
if (current_node->is_leaf) {
out_data[out_pos++] = current_node->symbol;
current_node = root;
}
}
*out_size = out_pos;
huffman_tree_free(root);
return ESP_OK;
}
eaf_dec_type_t eaf_dec_probe_frame_info(eaf_dec_handle_t handle, int frame_index)
{
if (!handle) {
GFX_LOGE(TAG, "Invalid handle");
return EAF_DEC_TYPE_INVALID;
}
const uint8_t *file_data = eaf_dec_get_frame_data(handle, frame_index);
if (!file_data) {
GFX_LOGE(TAG, "Frame %d data unavailable", frame_index);
return EAF_DEC_TYPE_INVALID;
}
size_t file_size = eaf_dec_get_frame_size(handle, frame_index);
if (file_size <= 0) {
GFX_LOGE(TAG, "Frame %d invalid size", frame_index);
return EAF_DEC_TYPE_INVALID;
}
eaf_dec_header_t header;
memset(&header, 0, sizeof(eaf_dec_header_t));
memcpy(header.format, file_data, 2);
header.format[2] = '\0';
if (strncmp(header.format, "_S", 2) == 0) {
memcpy(header.version, file_data + EAF_FRAME_VERSION_OFFSET, 6);
header.bit_depth = file_data[EAF_FRAME_BIT_DEPTH_OFFSET];
if (header.bit_depth != EAF_COLOR_DEPTH_4BIT && header.bit_depth != EAF_COLOR_DEPTH_8BIT && header.bit_depth != EAF_COLOR_DEPTH_24BIT) {
GFX_LOGE(TAG, "Invalid bit depth: %d", header.bit_depth);
return EAF_DEC_TYPE_INVALID;
}
header.width = *(uint16_t *)(file_data + EAF_FRAME_WIDTH_OFFSET);
header.height = *(uint16_t *)(file_data + EAF_FRAME_HEIGHT_OFFSET);
header.blocks = *(uint16_t *)(file_data + EAF_FRAME_BLOCKS_OFFSET);
header.block_height = *(uint16_t *)(file_data + EAF_FRAME_BLOCK_HEIGHT_OFFSET);
if (header.width == 0 || header.height == 0 || header.blocks == 0 || header.block_height == 0) {
return EAF_DEC_TYPE_INVALID;
}
} else if (strncmp(header.format, "_C", 2) == 0) {
return EAF_DEC_TYPE_FLAG;
} else {
return EAF_DEC_TYPE_INVALID;
}
return EAF_DEC_TYPE_VALID;
}
eaf_dec_type_t eaf_dec_get_frame_info(eaf_dec_handle_t handle, int frame_index, eaf_dec_header_t *header)
{
if (!handle) {
GFX_LOGE(TAG, "Invalid handle");
return EAF_DEC_TYPE_INVALID;
}
const uint8_t *file_data = eaf_dec_get_frame_data(handle, frame_index);
if (!file_data) {
GFX_LOGE(TAG, "Frame %d data unavailable", frame_index);
return EAF_DEC_TYPE_INVALID;
}
size_t file_size = eaf_dec_get_frame_size(handle, frame_index);
if (file_size <= 0) {
GFX_LOGE(TAG, "Frame %d invalid size", frame_index);
return EAF_DEC_TYPE_INVALID;
}
memset(header, 0, sizeof(eaf_dec_header_t));
memcpy(header->format, file_data, 2);
header->format[2] = '\0';
if (strncmp(header->format, "_S", 2) == 0) {
memcpy(header->version, file_data + EAF_FRAME_VERSION_OFFSET, 6);
header->bit_depth = file_data[EAF_FRAME_BIT_DEPTH_OFFSET];
if (header->bit_depth != EAF_COLOR_DEPTH_4BIT && header->bit_depth != EAF_COLOR_DEPTH_8BIT && header->bit_depth != EAF_COLOR_DEPTH_24BIT) {
GFX_LOGE(TAG, "Invalid bit depth: %d", header->bit_depth);
return EAF_DEC_TYPE_INVALID;
}
header->width = *(uint16_t *)(file_data + EAF_FRAME_WIDTH_OFFSET);
header->height = *(uint16_t *)(file_data + EAF_FRAME_HEIGHT_OFFSET);
header->blocks = *(uint16_t *)(file_data + EAF_FRAME_BLOCKS_OFFSET);
header->block_height = *(uint16_t *)(file_data + EAF_FRAME_BLOCK_HEIGHT_OFFSET);
header->block_len = (uint32_t *)malloc(header->blocks * sizeof(uint32_t));
if (header->block_len == NULL) {
GFX_LOGE(TAG, "No mem for block_len");
return EAF_DEC_TYPE_INVALID;
}
for (int i = 0; i < header->blocks; i++) {
header->block_len[i] = *(uint32_t *)(file_data + EAF_FRAME_BLOCK_LEN_TABLE_OFFSET + i * EAF_FRAME_BLOCK_LEN_SIZE);
}
header->num_colors = 1 << header->bit_depth;
if (header->bit_depth == EAF_COLOR_DEPTH_24BIT) {
header->num_colors = 0;
header->palette = NULL;
} else {
header->palette = (uint8_t *)malloc(header->num_colors * EAF_FRAME_PALETTE_ENTRY_SIZE);
if (header->palette == NULL) {
GFX_LOGE(TAG, "No mem for palette");
free(header->block_len);
header->block_len = NULL;
return EAF_DEC_TYPE_INVALID;
}
memcpy(header->palette, file_data + EAF_FRAME_BLOCK_LEN_TABLE_OFFSET + header->blocks * EAF_FRAME_BLOCK_LEN_SIZE, header->num_colors * EAF_FRAME_PALETTE_ENTRY_SIZE);
}
header->data_offset = EAF_FRAME_BLOCK_LEN_TABLE_OFFSET + header->blocks * EAF_FRAME_BLOCK_LEN_SIZE + header->num_colors * EAF_FRAME_PALETTE_ENTRY_SIZE;
return EAF_DEC_TYPE_VALID;
} else if (strncmp(header->format, "_C", 2) == 0) {
return EAF_DEC_TYPE_FLAG;
} else {
GFX_LOGE(TAG, "Invalid format: %s", header->format);
return EAF_DEC_TYPE_INVALID;
}
}
void eaf_dec_free_header(eaf_dec_header_t *header)
{
if (header->block_len != NULL) {
free(header->block_len);
header->block_len = NULL;
}
if (header->palette != NULL) {
free(header->palette);
header->palette = NULL;
}
}
void eaf_dec_calculate_offsets(const eaf_dec_header_t *header, uint32_t *offsets)
{
offsets[0] = header->data_offset;
for (int i = 1; i < header->blocks; i++) {
offsets[i] = offsets[i - 1] + header->block_len[i - 1];
}
}
/**********************
* PALETTE FUNCTIONS
**********************/
bool eaf_dec_get_palette_color(const eaf_dec_header_t *header, uint8_t color_index, bool swap_bytes, gfx_color_t *result)
{
const uint8_t *color_data = &header->palette[color_index * 4];
if (color_data[0] == 0 && color_data[1] == 0 && color_data[2] == 0 && color_data[3] == 0) {
return true;
}
gfx_color_t color = {
.full = (uint16_t)(((color_data[2] & 0xF8) << 8) |
((color_data[1] & 0xFC) << 3) |
((color_data[0] & 0xF8) >> 3)),
};
result->full = gfx_color_to_native_u16(color, swap_bytes);
return false;
}
/**********************
* DECODING FUNCTIONS
**********************/
static esp_err_t decode_huffman_rle(const uint8_t *in_data, size_t in_size,
uint8_t *out_data, size_t *out_size,
bool swap_color)
{
if (out_size == NULL || *out_size == 0) {
GFX_LOGE(TAG, "Output size is invalid");
return ESP_FAIL;
}
size_t tmp_size = *out_size * 2;
uint8_t *tmp_data = malloc(tmp_size);
if (tmp_data == NULL) {
GFX_LOGE(TAG, "No mem for tmp buffer");
return ESP_FAIL;
}
size_t tmp_len = tmp_size;
esp_err_t ret = eaf_dec_decode_huffman(in_data, in_size, tmp_data, &tmp_len, swap_color);
if (ret == ESP_OK) {
ret = eaf_dec_decode_rle(tmp_data, tmp_len, out_data, out_size, swap_color);
}
free(tmp_data);
return ret;
}
static esp_err_t register_decoder(eaf_dec_encoding_type_t type, eaf_dec_block_decoder_cb_t decoder)
{
if (type >= EAF_DEC_ENCODING_MAX) {
GFX_LOGE(TAG, "Invalid encoding type: %d", type);
return ESP_ERR_INVALID_ARG;
}
if (s_eaf_decoders[type] != NULL) {
GFX_LOGW(TAG, "Decoder already registered for type: %d", type);
}
s_eaf_decoders[type] = decoder;
return ESP_OK;
}
static esp_err_t init_decoders(void)
{
esp_err_t ret = ESP_OK;
ret |= register_decoder(EAF_DEC_ENCODING_RLE, eaf_dec_decode_rle);
ret |= register_decoder(EAF_DEC_ENCODING_HUFFMAN, decode_huffman_rle);
ret |= register_decoder(EAF_DEC_ENCODING_HUFFMAN_DIRECT, eaf_dec_decode_huffman);
#if CONFIG_GFX_EAF_JPEG_DECODE_SUPPORT
ret |= register_decoder(EAF_DEC_ENCODING_JPEG, eaf_dec_decode_jpeg);
#endif
#ifdef CONFIG_GFX_EAF_HEATSHRINK_SUPPORT
ret |= register_decoder(EAF_DEC_ENCODING_HEATSHRINK, eaf_dec_decode_heatshrink);
#endif
ret |= register_decoder(EAF_DEC_ENCODING_RAW, eaf_dec_decode_raw);
return ret;
}
esp_err_t eaf_dec_decode_block(const eaf_dec_header_t *header, const uint8_t *block_data,
int block_len, uint8_t *out_data, bool swap_color)
{
uint8_t encoding_type = block_data[0];
int width = header->width;
int block_height = header->block_height;
esp_err_t decode_result = ESP_FAIL;
if (encoding_type >= EAF_DEC_ENCODING_MAX) {
GFX_LOGE(TAG, "Unknown encoding type: %02X", encoding_type);
return ESP_FAIL;
}
eaf_dec_block_decoder_cb_t decoder = s_eaf_decoders[encoding_type];
if (!decoder) {
GFX_LOGE(TAG, "No decoder for encoding type: %02X", encoding_type);
return ESP_FAIL;
}
size_t out_size;
#if CONFIG_GFX_EAF_JPEG_DECODE_SUPPORT
if (encoding_type == EAF_DEC_ENCODING_JPEG) {
out_size = width * block_height * 2;
} else {
out_size = width * block_height;
}
#else
out_size = width * block_height;
#endif
decode_result = decoder(block_data + 1, block_len - 1, out_data, &out_size, swap_color);
if (decode_result != ESP_OK) {
return ESP_FAIL;
}
return ESP_OK;
}
esp_err_t eaf_dec_decode_rle(const uint8_t *in_data, size_t in_size,
uint8_t *out_data, size_t *out_size,
bool swap_color)
{
(void)swap_color;
size_t in_pos = 0;
size_t out_pos = 0;
while (in_pos + 1 < in_size) {
uint8_t repeat_count = in_data[in_pos++];
uint8_t repeat_value = in_data[in_pos++];
if (out_pos + repeat_count > *out_size) {
GFX_LOGE(TAG, "Decompressed buffer overflow, %zu > %zu", out_pos + repeat_count, *out_size);
return ESP_FAIL;
}
uint32_t value_4bytes = repeat_value | (repeat_value << 8) | (repeat_value << 16) | (repeat_value << 24);
while (repeat_count >= 4) {
*((uint32_t *)(out_data + out_pos)) = value_4bytes;
out_pos += 4;
repeat_count -= 4;
}
while (repeat_count > 0) {
out_data[out_pos++] = repeat_value;
repeat_count--;
}
}
*out_size = out_pos;
return ESP_OK;
}
esp_err_t eaf_dec_decode_raw(const uint8_t *in_data, size_t in_size,
uint8_t *out_data, size_t *out_size,
bool swap_color)
{
(void)swap_color;
if (!in_data || !out_data || !out_size) {
GFX_LOGE(TAG, "Invalid parameters");
return ESP_FAIL;
}
if (*out_size < in_size) {
GFX_LOGE(TAG, "Output buffer too small: need %zu, got %zu", in_size, *out_size);
return ESP_ERR_INVALID_SIZE;
}
if (in_size > 0) {
memcpy(out_data, in_data, in_size);
}
*out_size = in_size;
return ESP_OK;
}
#ifdef CONFIG_GFX_EAF_HEATSHRINK_SUPPORT
esp_err_t eaf_dec_decode_heatshrink(const uint8_t *in_data, size_t in_size,
uint8_t *out_data, size_t *out_size,
bool swap_color)
{
(void)swap_color;
if (!in_data || !out_data || !out_size) {
GFX_LOGE(TAG, "Invalid parameters");
return ESP_FAIL;
}
size_t out_capacity = *out_size;
if (out_capacity == 0) {
return ESP_OK;
}
#if CONFIG_HEATSHRINK_DYNAMIC_ALLOC
heatshrink_decoder *hsd = heatshrink_decoder_alloc(32, 8, 4);
if (!hsd) {
GFX_LOGE(TAG, "No mem for heatshrink decoder");
return ESP_ERR_NO_MEM;
}
#else
heatshrink_decoder hsd_stack;
heatshrink_decoder *hsd = &hsd_stack;
#endif
heatshrink_decoder_reset(hsd);
size_t in_pos = 0;
size_t out_pos = 0;
while (in_pos < in_size) {
size_t sunk = 0;
HSD_sink_res sres = heatshrink_decoder_sink(hsd, (uint8_t *)(in_data + in_pos),
in_size - in_pos, &sunk);
if (sres < 0) {
GFX_LOGE(TAG, "Heatshrink sink error: %d", sres);
goto hs_fail;
}
in_pos += sunk;
while (true) {
size_t produced = 0;
size_t remain = out_capacity - out_pos;
if (remain == 0) {
GFX_LOGE(TAG, "Heatshrink output overflow");
goto hs_fail;
}
HSD_poll_res press = heatshrink_decoder_poll(hsd, out_data + out_pos, remain, &produced);
if (press < 0) {
GFX_LOGE(TAG, "Heatshrink poll error: %d", press);
goto hs_fail;
}
out_pos += produced;
if (press == HSDR_POLL_EMPTY) {
break;
}
}
}
while (true) {
HSD_finish_res fres = heatshrink_decoder_finish(hsd);
if (fres < 0) {
GFX_LOGE(TAG, "Heatshrink finish error: %d", fres);
goto hs_fail;
}
while (true) {
size_t produced = 0;
size_t remain = out_capacity - out_pos;
if (remain == 0) {
GFX_LOGE(TAG, "Heatshrink output overflow");
goto hs_fail;
}
HSD_poll_res press = heatshrink_decoder_poll(hsd, out_data + out_pos, remain, &produced);
if (press < 0) {
GFX_LOGE(TAG, "Heatshrink poll error: %d", press);
goto hs_fail;
}
out_pos += produced;
if (press == HSDR_POLL_EMPTY) {
break;
}
}
if (fres == HSDR_FINISH_DONE) {
break;
}
}
*out_size = out_pos;
#if CONFIG_HEATSHRINK_DYNAMIC_ALLOC
heatshrink_decoder_free(hsd);
#endif
return ESP_OK;
hs_fail:
#if CONFIG_HEATSHRINK_DYNAMIC_ALLOC
heatshrink_decoder_free(hsd);
#endif
return ESP_FAIL;
}
#endif // CONFIG_GFX_EAF_HEATSHRINK_SUPPORT
#if CONFIG_GFX_EAF_JPEG_DECODE_SUPPORT
esp_err_t eaf_dec_decode_jpeg(const uint8_t *in_data, size_t in_size,
uint8_t *out_data, size_t *out_size, bool swap_color)
{
esp_err_t ret = ESP_OK;
uint32_t w, h;
jpeg_dec_handle_t jpeg_dec = NULL;
jpeg_dec_io_t *jpeg_io = NULL;
jpeg_dec_header_info_t *out_info = NULL;
jpeg_dec_config_t config = {
.output_type = swap_color ? JPEG_PIXEL_FORMAT_RGB565_BE : JPEG_PIXEL_FORMAT_RGB565_LE,
.rotate = JPEG_ROTATE_0D,
};
ESP_GOTO_ON_ERROR(jpeg_dec_open(&config, &jpeg_dec), err, TAG, "JPEG decoder open failed");
jpeg_io = malloc(sizeof(jpeg_dec_io_t));
ESP_GOTO_ON_FALSE(jpeg_io, ESP_ERR_NO_MEM, err, TAG, "No mem for jpeg_io");
out_info = malloc(sizeof(jpeg_dec_header_info_t));
ESP_GOTO_ON_FALSE(out_info, ESP_ERR_NO_MEM, err, TAG, "No mem for out_info");
jpeg_io->inbuf = (unsigned char *)in_data;
jpeg_io->inbuf_len = in_size;
jpeg_error_t jpeg_ret = jpeg_dec_parse_header(jpeg_dec, jpeg_io, out_info);
ESP_GOTO_ON_FALSE(jpeg_ret == JPEG_ERR_OK, ESP_FAIL, err, TAG, "JPEG header parse failed");
w = out_info->width;
h = out_info->height;
size_t required_size = w * h * 2;
ESP_GOTO_ON_FALSE(*out_size >= required_size, ESP_ERR_INVALID_SIZE, err, TAG,
"Buffer too small: need %zu, got %zu", required_size, *out_size);
jpeg_io->outbuf = out_data;
jpeg_ret = jpeg_dec_process(jpeg_dec, jpeg_io);
ESP_GOTO_ON_FALSE(jpeg_ret == JPEG_ERR_OK, ESP_FAIL, err, TAG, "JPEG decode failed: %d", jpeg_ret);
*out_size = required_size;
free(jpeg_io);
free(out_info);
jpeg_dec_close(jpeg_dec);
return ESP_OK;
err:
if (jpeg_io) {
free(jpeg_io);
}
if (out_info) {
free(out_info);
}
if (jpeg_dec) {
jpeg_dec_close(jpeg_dec);
}
return ret;
}
#endif // CONFIG_GFX_EAF_JPEG_DECODE_SUPPORT
esp_err_t eaf_dec_decode_huffman(const uint8_t *in_data, size_t in_size,
uint8_t *out_data, size_t *out_size,
bool swap_color)
{
(void)swap_color;
size_t out_len = *out_size;
if (!in_data || in_size < 3 || !out_data) {
GFX_LOGE(TAG, "Invalid parameters");
return ESP_FAIL;
}
uint16_t dict_size = (in_data[1] << 8) | in_data[0];
if (in_size < 2 + dict_size) {
GFX_LOGE(TAG, "Compressed data too short for dictionary");
return ESP_FAIL;
}
size_t encoded_size = in_size - 2 - dict_size;
esp_err_t ret = ESP_OK;
// Special case: when the block is single color, the dictionary may contain only one symbol and the data length is 0
if (encoded_size == 0) {
size_t dict_pos = 1; // dict_bytes[0] is padding
int symbol_count = 0;
uint8_t single_symbol = 0;
const uint8_t *dict_bytes = in_data + 2;
while (dict_pos < dict_size) {
uint8_t byte_val = dict_bytes[dict_pos++];
uint8_t code_len = dict_bytes[dict_pos++];
size_t code_byte_len = (size_t)((code_len + 7) / 8);
if (dict_pos + code_byte_len > dict_size) {
break;
}
dict_pos += code_byte_len;
symbol_count++;
single_symbol = byte_val;
if (symbol_count > 1) {
break;
}
}
if (symbol_count == 1) {
memset(out_data, single_symbol, out_len);
}
} else {
ret = huffman_decode_data(in_data + 2 + dict_size, encoded_size,
in_data + 2, dict_size,
out_data, &out_len);
}
if (ret != ESP_OK) {
GFX_LOGE(TAG, "Huffman decoding failed: %d", ret);
return ESP_FAIL;
}
if (out_len > *out_size) {
GFX_LOGE(TAG, "Decoded data too large: %zu > %zu", out_len, *out_size);
return ESP_FAIL;
}
*out_size = out_len;
return ESP_OK;
}
/**********************
* FORMAT FUNCTIONS
**********************/
esp_err_t eaf_dec_init(const uint8_t *data, size_t data_len, eaf_dec_handle_t *ret_parser)
{
static bool decoders_initialized = false;
if (!decoders_initialized) {
esp_err_t ret = init_decoders();
if (ret != ESP_OK) {
GFX_LOGE(TAG, "Decoder init failed");
return ret;
}
decoders_initialized = true;
}
esp_err_t ret = ESP_OK;
eaf_dec_frame_entry_t *entries = NULL;
eaf_dec_ctx_t *parser = (eaf_dec_ctx_t *)calloc(1, sizeof(eaf_dec_ctx_t));
ESP_GOTO_ON_FALSE(parser, ESP_ERR_NO_MEM, err, TAG, "no mem for parser handle");
ESP_GOTO_ON_FALSE(data[EAF_FORMAT_OFFSET] == EAF_FORMAT_MAGIC, ESP_ERR_INVALID_CRC, err, TAG, "bad file format magic");
const char *format_str = (const char *)(data + EAF_STR_OFFSET);
bool is_valid = (memcmp(format_str, EAF_FORMAT_STR, 3) == 0) || (memcmp(format_str, AAF_FORMAT_STR, 3) == 0);
ESP_GOTO_ON_FALSE(is_valid, ESP_ERR_INVALID_CRC, err, TAG, "bad file format string (expected EAF or AAF)");
int total_frames = *(int *)(data + EAF_NUM_OFFSET);
uint32_t stored_chk = *(uint32_t *)(data + EAF_CHECKSUM_OFFSET);
uint32_t stored_len = *(uint32_t *)(data + EAF_TABLE_LEN);
uint32_t calculated_chk = dec_calculate_checksum((uint8_t *)(data + EAF_TABLE_OFFSET), stored_len);
ESP_GOTO_ON_FALSE(calculated_chk == stored_chk, ESP_ERR_INVALID_CRC, err, TAG, "bad full checksum");
entries = (eaf_dec_frame_entry_t *)malloc(sizeof(eaf_dec_frame_entry_t) * total_frames);
eaf_dec_frame_table_entry_t *table = (eaf_dec_frame_table_entry_t *)(data + EAF_TABLE_OFFSET);
for (int i = 0; i < total_frames; i++) {
(entries + i)->table = (table + i);
(entries + i)->frame_mem = (void *)(data + EAF_TABLE_OFFSET + total_frames * sizeof(eaf_dec_frame_table_entry_t) + table[i].frame_offset);
uint16_t *magic_ptr = (uint16_t *)(entries + i)->frame_mem;
ESP_GOTO_ON_FALSE(*magic_ptr == EAF_MAGIC_HEAD, ESP_ERR_INVALID_CRC, err, TAG, "bad file magic header");
}
parser->entries = entries;
parser->total_frames = total_frames;
*ret_parser = (eaf_dec_handle_t)parser;
return ESP_OK;
err:
if (entries) {
free(entries);
}
if (parser) {
free(parser);
}
*ret_parser = NULL;
return ret;
}
esp_err_t eaf_dec_deinit(eaf_dec_handle_t handle)
{
if (handle == NULL) {
return ESP_ERR_INVALID_ARG;
}
eaf_dec_ctx_t *parser = (eaf_dec_ctx_t *)(handle);
if (parser) {
if (parser->entries) {
free(parser->entries);
}
free(parser);
}
return ESP_OK;
}
int eaf_dec_get_total_frames(eaf_dec_handle_t handle)
{
if (handle == NULL) {
GFX_LOGE(TAG, "Handle is invalid");
return -1;
}
eaf_dec_ctx_t *parser = (eaf_dec_ctx_t *)(handle);
return parser->total_frames;
}
const uint8_t *eaf_dec_get_frame_data(eaf_dec_handle_t handle, int index)
{
if (handle == NULL) {
GFX_LOGE(TAG, "Handle is invalid");
return NULL;
}
eaf_dec_ctx_t *parser = (eaf_dec_ctx_t *)(handle);
if (parser->total_frames > index) {
return (const uint8_t *)((parser->entries + index)->frame_mem + EAF_MAGIC_LEN);
} else {
GFX_LOGE(TAG, "Invalid index: %d. Maximum index is %d.", index, parser->total_frames);
return NULL;
}
}
int eaf_dec_get_frame_size(eaf_dec_handle_t handle, int index)
{
if (handle == NULL) {
GFX_LOGE(TAG, "Handle is invalid");
return -1;
}
eaf_dec_ctx_t *parser = (eaf_dec_ctx_t *)(handle);
if (parser->total_frames > index) {
return ((parser->entries + index)->table->frame_size - EAF_MAGIC_LEN);
} else {
GFX_LOGE(TAG, "Invalid index: %d. Maximum index is %d.", index, parser->total_frames);
return -1;
}
}
esp_err_t eaf_dec_decode_frame(eaf_dec_handle_t handle, int frame_index,
uint8_t *out_data, size_t out_size,
bool swap_bytes)
{
if (!handle || !out_data) {
return ESP_ERR_INVALID_STATE;
}
const uint8_t *frame_data = eaf_dec_get_frame_data(handle, frame_index);
if (!frame_data) {
GFX_LOGE(TAG, "Frame %d data unavailable", frame_index);
return ESP_FAIL;
}
eaf_dec_header_t header;
eaf_dec_type_t format = eaf_dec_get_frame_info(handle, frame_index, &header);
if (format != EAF_DEC_TYPE_VALID) {
GFX_LOGE(TAG, "Frame %d header parse failed", frame_index);
return ESP_FAIL;
}
size_t block_height = header.block_height;
size_t width = header.width;
size_t height = header.height;
uint8_t bit_depth = header.bit_depth;
size_t block_size = width * block_height;
block_size = (bit_depth == EAF_COLOR_DEPTH_24BIT) ? block_size * 2 : block_size;
uint32_t *offsets = (uint32_t *)malloc(header.blocks * sizeof(uint32_t));
if (offsets == NULL) {
GFX_LOGE(TAG, "No mem for block offsets");
eaf_dec_free_header(&header);
return ESP_ERR_NO_MEM;
}
eaf_dec_calculate_offsets(&header, offsets);
uint8_t *tmp_data = malloc(block_size);
if (!tmp_data) {
GFX_LOGE(TAG, "No mem for block buffer");
free(offsets);
eaf_dec_free_header(&header);
return ESP_ERR_NO_MEM;
}
uint32_t palette_cache[256];
memset(palette_cache, 0xFF, sizeof(palette_cache));
for (int block = 0; block < header.blocks; block++) {
const uint8_t *block_data = frame_data + offsets[block];
int block_len = header.block_len[block];
esp_err_t ret = eaf_dec_decode_block(&header, block_data, block_len, tmp_data, swap_bytes);
if (ret != ESP_OK) {
GFX_LOGD(TAG, "Block %d decode failed", block);
continue;
}
uint16_t *block_buffer = (uint16_t *)out_data + (block * block_height * width);
size_t valid_size;
if ((block + 1) * block_height > height) {
valid_size = (height - block * block_height) * width;
valid_size = (bit_depth == EAF_COLOR_DEPTH_24BIT) ? valid_size * 2 : valid_size;
} else {
valid_size = block_size;
}
if (bit_depth == EAF_COLOR_DEPTH_8BIT) {
for (size_t i = 0; i < valid_size; i++) {
uint8_t index = tmp_data[i];
uint16_t color;
if (palette_cache[index] == 0xFFFFFFFF) {
gfx_color_t eaf_color;
eaf_dec_get_palette_color(&header, index, swap_bytes, &eaf_color);
palette_cache[index] = eaf_color.full;
color = eaf_color.full;
} else {
color = palette_cache[index];
}
block_buffer[i] = color;
}
} else if (bit_depth == EAF_COLOR_DEPTH_4BIT) {
GFX_LOGW(TAG, "%d-bit depth not supported", EAF_COLOR_DEPTH_4BIT);
} else if (bit_depth == EAF_COLOR_DEPTH_24BIT) {
memcpy(block_buffer, tmp_data, valid_size);
}
}
free(tmp_data);
free(offsets);
eaf_dec_free_header(&header);
return ESP_OK;
}

View File

@ -0,0 +1,308 @@
/*
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "sdkconfig.h"
#include "esp_err.h"
#include "core/gfx_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**********************
* DEFINES
**********************/
#define EAF_MAGIC_HEAD 0x5A5A
#define EAF_MAGIC_LEN 2
#define EAF_FORMAT_MAGIC 0x89
#define EAF_FORMAT_STR "EAF"
#define AAF_FORMAT_STR "AAF"
#define EAF_FORMAT_OFFSET 0
#define EAF_STR_OFFSET 1
#define EAF_NUM_OFFSET 4
#define EAF_CHECKSUM_OFFSET 8
#define EAF_TABLE_LEN 12
#define EAF_TABLE_OFFSET 16
#define EAF_COLOR_DEPTH_4BIT 4
#define EAF_COLOR_DEPTH_8BIT 8
#define EAF_COLOR_DEPTH_24BIT 24
/**********************
* TYPEDEFS
**********************/
#pragma pack(1)
typedef struct {
uint32_t frame_size; /*!< Size of the frame */
uint32_t frame_offset; /*!< Offset of the frame */
} eaf_dec_frame_table_entry_t;
#pragma pack()
typedef struct {
const char *frame_mem;
const eaf_dec_frame_table_entry_t *table;
} eaf_dec_frame_entry_t;
typedef struct {
eaf_dec_frame_entry_t *entries;
int total_frames;
} eaf_dec_ctx_t;
typedef enum {
EAF_DEC_TYPE_VALID = 0, /*!< Valid EAF format with split BMP data */
EAF_DEC_TYPE_REDIRECT = 1, /*!< Redirect format pointing to another file */
EAF_DEC_TYPE_INVALID = 2, /*!< Invalid or unsupported format */
EAF_DEC_TYPE_FLAG = 3 /*!< Invalid format */
} eaf_dec_type_t;
typedef enum {
EAF_DEC_ENCODING_RLE = 0, /*!< Run-Length Encoding */
EAF_DEC_ENCODING_HUFFMAN = 1, /*!< Huffman encoding with RLE */
EAF_DEC_ENCODING_JPEG = 2, /*!< JPEG encoding */
EAF_DEC_ENCODING_HUFFMAN_DIRECT = 3, /*!< Direct Huffman encoding without RLE */
EAF_DEC_ENCODING_HEATSHRINK = 4, /*!< Heatshrink encoding */
EAF_DEC_ENCODING_RAW = 5, /*!< Raw (uncompressed) */
EAF_DEC_ENCODING_MAX /*!< Maximum number of encoding types */
} eaf_dec_encoding_type_t;
typedef struct {
char format[3]; /*!< Format identifier (e.g., "_S") */
char version[6]; /*!< Version string */
uint8_t bit_depth; /*!< Bit depth (4, 8, or 24) */
uint16_t width; /*!< Image width in pixels */
uint16_t height; /*!< Image height in pixels */
uint16_t blocks; /*!< Number of blocks */
uint16_t block_height; /*!< Height of each block */
uint32_t *block_len; /*!< Data length of each block */
uint16_t data_offset; /*!< Offset to data segment */
uint8_t *palette; /*!< Color palette (dynamically allocated) */
int num_colors; /*!< Number of colors in palette */
} eaf_dec_header_t;
typedef struct eaf_dec_huffman_node {
uint8_t is_leaf; /*!< Whether this node is a leaf node */
uint8_t symbol; /*!< Symbol value for leaf nodes */
struct eaf_dec_huffman_node *left; /*!< Left child node */
struct eaf_dec_huffman_node *right; /*!< Right child node */
} eaf_dec_huffman_node_t;
/**
* @brief EAF format parser handle
*/
typedef void *eaf_dec_handle_t;
/**********************
* PUBLIC API
**********************/
/**
* @brief Probe the header of an EAF file
* @param handle Parser handle
* @param frame_index Frame index
* @return Image format type (VALID, FLAG, or INVALID)
*/
eaf_dec_type_t eaf_dec_probe_frame_info(eaf_dec_handle_t handle, int frame_index);
/**
* @brief Parse the header of an EAF file
* @param handle Parser handle
* @param frame_index Frame index
* @param header Pointer to store the parsed header information
* @return Image format type (VALID, REDIRECT, or INVALID)
*/
eaf_dec_type_t eaf_dec_get_frame_info(eaf_dec_handle_t handle, int frame_index, eaf_dec_header_t *header);
/**
* @brief Free resources allocated for EAF header
* @param header Pointer to the header structure
*/
void eaf_dec_free_header(eaf_dec_header_t *header);
/**
* @brief Calculate block offsets from header information
* @param header Pointer to the header structure
* @param offsets Array to store calculated offsets
*/
void eaf_dec_calculate_offsets(const eaf_dec_header_t *header, uint32_t *offsets);
/**********************
* COLOR OPERATIONS
**********************/
/**
* @brief Get color from palette at specified index
* @param header Pointer to the header structure containing palette
* @param color_index Index in the palette
* @param swap_bytes Whether the returned RGB565 value should be converted to
* native framebuffer byte order for the current draw target
* @param result Output parameter for semantic/native RGB565 color value
* @return true if color is fully transparent (00 00 00 00), false otherwise
*/
bool eaf_dec_get_palette_color(const eaf_dec_header_t *header, uint8_t color_index, bool swap_bytes, gfx_color_t *result);
/**********************
* COMPRESSION OPERATIONS
**********************/
typedef esp_err_t (*eaf_dec_block_decoder_cb_t)(const uint8_t *in_data, size_t in_size,
uint8_t *out_data, size_t *out_size,
bool swap_color);
/**
* @brief Decode RLE compressed data
* @param in_data Input compressed data
* @param in_size Size of compressed data
* @param out_data Output buffer for decompressed data
* @param out_size Size of output buffer
* @return ESP_OK on success, ESP_FAIL on failure
*/
esp_err_t eaf_dec_decode_rle(const uint8_t *in_data, size_t in_size,
uint8_t *out_data, size_t *out_size,
bool swap_color);
/**
* @brief Decode Huffman compressed data
* @param in_data Input compressed data
* @param in_size Size of input data
* @param out_data Output buffer for decompressed data
* @param out_size Size of output buffer
* @param swap_color Whether decoded RGB565 data should be written in native
* framebuffer byte order for the current draw target (unused here)
* @return ESP_OK on success, ESP_FAIL on failure
*/
esp_err_t eaf_dec_decode_huffman(const uint8_t *in_data, size_t in_size,
uint8_t *out_data, size_t *out_size,
bool swap_color);
#ifdef CONFIG_GFX_EAF_HEATSHRINK_SUPPORT
/**
* @brief Decode heatshrink compressed data
* @param in_data Input compressed data
* @param in_size Size of input data
* @param out_data Output buffer for decompressed data
* @param out_size Size of output buffer
* @param swap_color Whether decoded RGB565 data should be written in native
* framebuffer byte order for the current draw target (unused here)
* @return ESP_OK on success, ESP_FAIL on failure
*/
esp_err_t eaf_dec_decode_heatshrink(const uint8_t *in_data, size_t in_size,
uint8_t *out_data, size_t *out_size,
bool swap_color);
#endif // CONFIG_GFX_EAF_HEATSHRINK_SUPPORT
/**
* @brief Decode raw (uncompressed) data
* @param in_data Input data
* @param in_size Size of input data
* @param out_data Output buffer for data
* @param out_size Size of output buffer
* @param swap_color Whether decoded RGB565 data should be written in native
* framebuffer byte order for the current draw target (unused here)
* @return ESP_OK on success, ESP_FAIL on failure
*/
esp_err_t eaf_dec_decode_raw(const uint8_t *in_data, size_t in_size,
uint8_t *out_data, size_t *out_size,
bool swap_color);
#if CONFIG_GFX_EAF_JPEG_DECODE_SUPPORT
/**
* @brief Decode JPEG compressed data
* @param in_data Input JPEG data
* @param in_size Size of input data
* @param out_data Output buffer for decoded data
* @param out_size Size of output buffer
* @param swap_color Whether decoded RGB565 data should be written in native
* framebuffer byte order for the current draw target
* @return ESP_OK on success, ESP_FAIL on failure
*/
esp_err_t eaf_dec_decode_jpeg(const uint8_t *in_data, size_t in_size,
uint8_t *out_data, size_t *out_size, bool swap_color);
#endif // CONFIG_GFX_EAF_JPEG_DECODE_SUPPORT
/**********************
* FRAME OPERATIONS
**********************/
/**
* @brief Decode a block of EAF data
* @param header EAF header information
* @param block_data Pointer to the block data
* @param block_len Length of the block
* @param out_data Buffer to store decoded data
* @param swap_color Whether decoded RGB565 data should be written in native
* framebuffer byte order for the current draw target
* @return ESP_OK on success, ESP_FAIL on failure
*/
esp_err_t eaf_dec_decode_block(const eaf_dec_header_t *header, const uint8_t *block_data,
int block_len, uint8_t *out_data, bool swap_color);
/**********************
* FORMAT OPERATIONS
**********************/
/**
* @brief Initialize EAF format parser
* @param data Pointer to EAF file data
* @param data_len Length of EAF file data
* @param ret_parser Pointer to store the parser handle
* @return ESP_OK on success, ESP_FAIL on failure
*/
esp_err_t eaf_dec_init(const uint8_t *data, size_t data_len, eaf_dec_handle_t *ret_parser);
/**
* @brief Deinitialize EAF format parser
* @param handle Parser handle
* @return ESP_OK on success, ESP_FAIL on failure
*/
esp_err_t eaf_dec_deinit(eaf_dec_handle_t handle);
/**
* @brief Get total number of frames in EAF file
* @param handle Parser handle
* @return Total number of frames
*/
int eaf_dec_get_total_frames(eaf_dec_handle_t handle);
/**
* @brief Get frame data at specified index
* @param handle Parser handle
* @param index Frame index
* @return Pointer to frame data, NULL on failure
*/
const uint8_t *eaf_dec_get_frame_data(eaf_dec_handle_t handle, int index);
/**
* @brief Get frame size at specified index
* @param handle Parser handle
* @param index Frame index
* @return Frame size in bytes, -1 on failure
*/
int eaf_dec_get_frame_size(eaf_dec_handle_t handle, int index);
/**
* @brief Decode a full EAF frame
* @param handle Format handle
* @param frame_index Index of the frame to decode
* @param out_data Output buffer for decoded frame
* @param out_size Size of output buffer
* @param swap_bytes Whether to swap color bytes
* @return ESP_OK on success, ESP_FAIL on failure
*/
esp_err_t eaf_dec_decode_frame(eaf_dec_handle_t handle, int frame_index,
uint8_t *out_data, size_t out_size,
bool swap_bytes);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,76 @@
# QR Code Wrapper Library
[![Component Registry](https://components.espressif.com/components/espressif/qrcode/badge.svg)](https://components.espressif.com/components/espressif/qrcode)
## Overview
This is a QR code generation wrapper library based on the [qrcodegen](https://www.nayuki.io/page/qr-code-generator-library) library, re-encapsulated from the [Espressif QR Code component](https://components.espressif.com/components/espressif/qrcode).
## Why Re-encapsulate?
To avoid dependency issues with the `espressif__qrcode` component, we directly integrated the core `qrcodegen` library into the project and provided an API interface compatible with the original component.
## File Structure
```
src/lib/qrcode/
├── qrcodegen.h # QR Code generator library header
├── qrcodegen.c # QR Code generator library implementation
├── qrcode_wrapper.h # Wrapper interface header
├── qrcode_wrapper.c # Wrapper interface implementation
└── README.md # This file
```
## Core Library
- **qrcodegen**: QR Code generator library developed by Project Nayuki
- License: MIT License
- Project URL: https://www.nayuki.io/page/qr-code-generator-library
- Supports QR Code Model 2 specification, versions 1-40, all 4 error correction levels
## API Interface
### Main Functions
- `qrcode_wrapper_generate()` - Generate QR code
- `qrcode_wrapper_get_size()` - Get QR code size
- `qrcode_wrapper_get_module()` - Get module value at specified coordinates
- `qrcode_wrapper_print_console()` - Print QR code to console (optional)
### Error Correction Levels
- `QRCODE_WRAPPER_ECC_LOW` - 7% error tolerance
- `QRCODE_WRAPPER_ECC_MED` - 15% error tolerance
- `QRCODE_WRAPPER_ECC_QUART` - 25% error tolerance
- `QRCODE_WRAPPER_ECC_HIGH` - 30% error tolerance
## Usage Example
```c
#include "lib/qrcode/qrcode_wrapper.h"
// Configure QR code generation parameters
qrcode_wrapper_config_t cfg = {
.display_func = my_display_callback,
.max_qrcode_version = 5,
.qrcode_ecc_level = QRCODE_WRAPPER_ECC_LOW,
.user_data = NULL
};
// Generate QR code
esp_err_t ret = qrcode_wrapper_generate(&cfg, "Hello, World!");
if (ret == ESP_OK) {
// QR code generated successfully
}
```
## License
- qrcodegen library: MIT License (Copyright (c) Project Nayuki)
- Wrapper code: Apache-2.0 (Copyright 2024-2025 Espressif Systems)
## References
- [Espressif QR Code Component](https://components.espressif.com/components/espressif/qrcode)
- [qrcodegen Library](https://www.nayuki.io/page/qr-code-generator-library)
- [QR Code ISO/IEC 18004 Standard](https://www.iso.org/standard/62021.html)

View File

@ -0,0 +1,134 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <esp_err.h>
#include <esp_log.h>
#define GFX_LOG_MODULE GFX_LOG_MODULE_QRCODE_LIB
#include "common/gfx_log_priv.h"
#include "qrcodegen.h"
#include "qrcode_wrapper.h"
static const char *TAG = "qrcode_lib";
static const char *lt[] = {
/* 0 */ " ",
/* 1 */ "\u2580 ",
/* 2 */ " \u2580",
/* 3 */ "\u2580\u2580",
/* 4 */ "\u2584 ",
/* 5 */ "\u2588 ",
/* 6 */ "\u2584\u2580",
/* 7 */ "\u2588\u2580",
/* 8 */ " \u2584",
/* 9 */ "\u2580\u2584",
/* 10 */ " \u2588",
/* 11 */ "\u2580\u2588",
/* 12 */ "\u2584\u2584",
/* 13 */ "\u2588\u2584",
/* 14 */ "\u2584\u2588",
/* 15 */ "\u2588\u2588",
};
void qrcode_wrapper_print_console(qrcode_wrapper_handle_t qrcode, void *user_data)
{
(void)user_data;
int size = qrcodegen_getSize(qrcode);
int border = 2;
unsigned char num = 0;
for (int y = -border; y < size + border; y += 2) {
for (int x = -border; x < size + border; x += 2) {
num = 0;
if (qrcodegen_getModule(qrcode, x, y)) {
num |= 1 << 0;
}
if ((x < size + border) && qrcodegen_getModule(qrcode, x + 1, y)) {
num |= 1 << 1;
}
if ((y < size + border) && qrcodegen_getModule(qrcode, x, y + 1)) {
num |= 1 << 2;
}
if ((x < size + border) && (y < size + border) && qrcodegen_getModule(qrcode, x + 1, y + 1)) {
num |= 1 << 3;
}
printf("%s", lt[num]);
}
printf("\n");
}
printf("\n");
}
int qrcode_wrapper_get_size(qrcode_wrapper_handle_t qrcode)
{
return qrcodegen_getSize(qrcode);
}
bool qrcode_wrapper_get_module(qrcode_wrapper_handle_t qrcode, int x, int y)
{
return qrcodegen_getModule(qrcode, x, y);
}
esp_err_t qrcode_wrapper_generate(qrcode_wrapper_config_t *cfg, const char *text)
{
enum qrcodegen_Ecc ecc_lvl;
uint8_t *qrcode, *tempbuf;
esp_err_t err = ESP_FAIL;
if (cfg == NULL || text == NULL) {
return ESP_ERR_INVALID_ARG;
}
qrcode = calloc(1, qrcodegen_BUFFER_LEN_FOR_VERSION(cfg->max_qrcode_version));
if (!qrcode) {
return ESP_ERR_NO_MEM;
}
tempbuf = calloc(1, qrcodegen_BUFFER_LEN_FOR_VERSION(cfg->max_qrcode_version));
if (!tempbuf) {
free(qrcode);
return ESP_ERR_NO_MEM;
}
switch (cfg->qrcode_ecc_level) {
case QRCODE_WRAPPER_ECC_LOW:
ecc_lvl = qrcodegen_Ecc_LOW;
break;
case QRCODE_WRAPPER_ECC_MED:
ecc_lvl = qrcodegen_Ecc_MEDIUM;
break;
case QRCODE_WRAPPER_ECC_QUART:
ecc_lvl = qrcodegen_Ecc_QUARTILE;
break;
case QRCODE_WRAPPER_ECC_HIGH:
ecc_lvl = qrcodegen_Ecc_HIGH;
break;
default:
ecc_lvl = qrcodegen_Ecc_LOW;
break;
}
GFX_LOGD(TAG, "Encoding text with ECC LVL %d & QR Code Version %d", ecc_lvl, cfg->max_qrcode_version);
GFX_LOGD(TAG, "%s", text);
// Make and print the QR Code symbol
bool ok = qrcodegen_encodeText(text, tempbuf, qrcode, ecc_lvl,
qrcodegen_VERSION_MIN, cfg->max_qrcode_version,
qrcodegen_Mask_AUTO, true);
if (ok && cfg->display_func) {
cfg->display_func((qrcode_wrapper_handle_t)qrcode, cfg->user_data);
err = ESP_OK;
} else if (!ok) {
GFX_LOGE(TAG, "Failed to encode QR Code");
err = ESP_FAIL;
}
free(qrcode);
free(tempbuf);
return err;
}

View File

@ -0,0 +1,101 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <esp_err.h>
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief QR Code handle used by the display function
*/
typedef const uint8_t *qrcode_wrapper_handle_t;
/**
* @brief QR Code configuration options
*/
typedef struct {
void (*display_func)(qrcode_wrapper_handle_t qrcode, void *user_data); /**< Function called for displaying the QR Code after encoding is complete */
int max_qrcode_version; /**< Max QR Code Version to be used. Range: 2 - 40 */
int qrcode_ecc_level; /**< Error Correction Level for QR Code */
void *user_data; /**< User data */
} qrcode_wrapper_config_t;
/**
* @brief Error Correction Level in a QR Code Symbol
*/
enum {
QRCODE_WRAPPER_ECC_LOW, /**< QR Code Error Tolerance of 7% */
QRCODE_WRAPPER_ECC_MED, /**< QR Code Error Tolerance of 15% */
QRCODE_WRAPPER_ECC_QUART, /**< QR Code Error Tolerance of 25% */
QRCODE_WRAPPER_ECC_HIGH /**< QR Code Error Tolerance of 30% */
};
/**
* @brief Encodes the given string into a QR Code and calls the display function
*
* @attention 1. Can successfully encode a UTF-8 string of up to 2953 bytes or an alphanumeric
* string of up to 4296 characters or any digit string of up to 7089 characters
*
* @param cfg Configuration used for QR Code encoding.
* @param text String to encode into a QR Code.
*
* @return
* - ESP_OK: succeed
* - ESP_FAIL: Failed to encode string into a QR Code
* - ESP_ERR_NO_MEM: Failed to allocate buffer for given max_qrcode_version
*/
esp_err_t qrcode_wrapper_generate(qrcode_wrapper_config_t *cfg, const char *text);
/**
* @brief Displays QR Code on the console
*
* @param qrcode QR Code handle used by the display function.
*/
void qrcode_wrapper_print_console(qrcode_wrapper_handle_t qrcode, void *user_data);
/**
* @brief Returns the side length of the given QR Code
*
* @param qrcode QR Code handle used by the display function.
*
* @return
* - val[21, 177]: Side length of QR Code
*/
int qrcode_wrapper_get_size(qrcode_wrapper_handle_t qrcode);
/**
* @brief Returns the Pixel value for the given coordinates
* False indicates White and True indicates Black
*
* @attention 1. Coordinates for top left corner are (x=0, y=0)
* @attention 2. For out of bound coordinates false (White) is returned
*
* @param qrcode QR Code handle used by the display function.
* @param x X-Coordinate of QR Code module
* @param y Y-Coordinate of QR Code module
*
* @return
* - true: (x, y) Pixel is Black
* - false: (x, y) Pixel is White
*/
bool qrcode_wrapper_get_module(qrcode_wrapper_handle_t qrcode, int x, int y);
#define QRCODE_WRAPPER_CONFIG_DEFAULT() (qrcode_wrapper_config_t) { \
.display_func = qrcode_wrapper_print_console, \
.max_qrcode_version = 10, \
.qrcode_ecc_level = QRCODE_WRAPPER_ECC_LOW, \
.user_data = NULL, \
}
#ifdef __cplusplus
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,311 @@
/*
* QR Code generator library (C)
*
* Copyright (c) Project Nayuki. (MIT License)
* https://www.nayuki.io/page/qr-code-generator-library
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
* - The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* - The Software is provided "as is", without warranty of any kind, express or
* implied, including but not limited to the warranties of merchantability,
* fitness for a particular purpose and noninfringement. In no event shall the
* authors or copyright holders be liable for any claim, damages or other
* liability, whether in an action of contract, tort or otherwise, arising from,
* out of or in connection with the Software or the use or other dealings in the
* Software.
*/
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/*
* This library creates QR Code symbols, which is a type of two-dimension barcode.
* Invented by Denso Wave and described in the ISO/IEC 18004 standard.
* A QR Code structure is an immutable square grid of black and white cells.
* The library provides functions to create a QR Code from text or binary data.
* The library covers the QR Code Model 2 specification, supporting all versions (sizes)
* from 1 to 40, all 4 error correction levels, and 4 character encoding modes.
*
* Ways to create a QR Code object:
* - High level: Take the payload data and call qrcodegen_encodeText() or qrcodegen_encodeBinary().
* - Low level: Custom-make the list of segments and call
* qrcodegen_encodeSegments() or qrcodegen_encodeSegmentsAdvanced().
* (Note that all ways require supplying the desired error correction level and various byte buffers.)
*/
/*---- Enum and struct types----*/
/*
* The error correction level in a QR Code symbol.
*/
enum qrcodegen_Ecc {
// Must be declared in ascending order of error protection
// so that an internal qrcodegen function works properly
qrcodegen_Ecc_LOW = 0, // The QR Code can tolerate about 7% erroneous codewords
qrcodegen_Ecc_MEDIUM, // The QR Code can tolerate about 15% erroneous codewords
qrcodegen_Ecc_QUARTILE, // The QR Code can tolerate about 25% erroneous codewords
qrcodegen_Ecc_HIGH, // The QR Code can tolerate about 30% erroneous codewords
};
/*
* The mask pattern used in a QR Code symbol.
*/
enum qrcodegen_Mask {
// A special value to tell the QR Code encoder to
// automatically select an appropriate mask pattern
qrcodegen_Mask_AUTO = -1,
// The eight actual mask patterns
qrcodegen_Mask_0 = 0,
qrcodegen_Mask_1,
qrcodegen_Mask_2,
qrcodegen_Mask_3,
qrcodegen_Mask_4,
qrcodegen_Mask_5,
qrcodegen_Mask_6,
qrcodegen_Mask_7,
};
/*
* Describes how a segment's data bits are interpreted.
*/
enum qrcodegen_Mode {
qrcodegen_Mode_NUMERIC = 0x1,
qrcodegen_Mode_ALPHANUMERIC = 0x2,
qrcodegen_Mode_BYTE = 0x4,
qrcodegen_Mode_KANJI = 0x8,
qrcodegen_Mode_ECI = 0x7,
};
/*
* A segment of character/binary/control data in a QR Code symbol.
* The mid-level way to create a segment is to take the payload data
* and call a factory function such as qrcodegen_makeNumeric().
* The low-level way to create a segment is to custom-make the bit buffer
* and initialize a qrcodegen_Segment struct with appropriate values.
* Even in the most favorable conditions, a QR Code can only hold 7089 characters of data.
* Any segment longer than this is meaningless for the purpose of generating QR Codes.
* Moreover, the maximum allowed bit length is 32767 because
* the largest QR Code (version 40) has 31329 modules.
*/
struct qrcodegen_Segment {
// The mode indicator of this segment.
enum qrcodegen_Mode mode;
// The length of this segment's unencoded data. Measured in characters for
// numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode.
// Always zero or positive. Not the same as the data's bit length.
int numChars;
// The data bits of this segment, packed in bitwise big endian.
// Can be null if the bit length is zero.
uint8_t *data;
// The number of valid data bits used in the buffer. Requires
// 0 <= bitLength <= 32767, and bitLength <= (capacity of data array) * 8.
// The character count (numChars) must agree with the mode and the bit buffer length.
int bitLength;
};
/*---- Macro constants and functions ----*/
#define qrcodegen_VERSION_MIN 1 // The minimum version number supported in the QR Code Model 2 standard
#define qrcodegen_VERSION_MAX 40 // The maximum version number supported in the QR Code Model 2 standard
// Calculates the number of bytes needed to store any QR Code up to and including the given version number,
// as a compile-time constant. For example, 'uint8_t buffer[qrcodegen_BUFFER_LEN_FOR_VERSION(25)];'
// can store any single QR Code from version 1 to 25 (inclusive). The result fits in an int (or int16).
// Requires qrcodegen_VERSION_MIN <= n <= qrcodegen_VERSION_MAX.
#define qrcodegen_BUFFER_LEN_FOR_VERSION(n) ((((n) * 4 + 17) * ((n) * 4 + 17) + 7) / 8 + 1)
// The worst-case number of bytes needed to store one QR Code, up to and including
// version 40. This value equals 3918, which is just under 4 kilobytes.
// Use this more convenient value to avoid calculating tighter memory bounds for buffers.
#define qrcodegen_BUFFER_LEN_MAX qrcodegen_BUFFER_LEN_FOR_VERSION(qrcodegen_VERSION_MAX)
/*---- Functions (high level) to generate QR Codes ----*/
/*
* Encodes the given text string to a QR Code, returning true if encoding succeeded.
* If the data is too long to fit in any version in the given range
* at the given ECC level, then false is returned.
* - The input text must be encoded in UTF-8 and contain no NULs.
* - The variables ecl and mask must correspond to enum constant values.
* - Requires 1 <= minVersion <= maxVersion <= 40.
* - The arrays tempBuffer and qrcode must each have a length
* of at least qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion).
* - After the function returns, tempBuffer contains no useful data.
* - If successful, the resulting QR Code may use numeric,
* alphanumeric, or byte mode to encode the text.
* - In the most optimistic case, a QR Code at version 40 with low ECC
* can hold any UTF-8 string up to 2953 bytes, or any alphanumeric string
* up to 4296 characters, or any digit string up to 7089 characters.
* These numbers represent the hard upper limit of the QR Code standard.
* - Please consult the QR Code specification for information on
* data capacities per version, ECC level, and text encoding mode.
*/
bool qrcodegen_encodeText(const char *text, uint8_t tempBuffer[], uint8_t qrcode[],
enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl);
/*
* Encodes the given binary data to a QR Code, returning true if encoding succeeded.
* If the data is too long to fit in any version in the given range
* at the given ECC level, then false is returned.
* - The input array range dataAndTemp[0 : dataLen] should normally be
* valid UTF-8 text, but is not required by the QR Code standard.
* - The variables ecl and mask must correspond to enum constant values.
* - Requires 1 <= minVersion <= maxVersion <= 40.
* - The arrays dataAndTemp and qrcode must each have a length
* of at least qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion).
* - After the function returns, the contents of dataAndTemp may have changed,
* and does not represent useful data anymore.
* - If successful, the resulting QR Code will use byte mode to encode the data.
* - In the most optimistic case, a QR Code at version 40 with low ECC can hold any byte
* sequence up to length 2953. This is the hard upper limit of the QR Code standard.
* - Please consult the QR Code specification for information on
* data capacities per version, ECC level, and text encoding mode.
*/
bool qrcodegen_encodeBinary(uint8_t dataAndTemp[], size_t dataLen, uint8_t qrcode[],
enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl);
/*---- Functions (low level) to generate QR Codes ----*/
/*
* Renders a QR Code representing the given segments at the given error correction level.
* The smallest possible QR Code version is automatically chosen for the output. Returns true if
* QR Code creation succeeded, or false if the data is too long to fit in any version. The ECC level
* of the result may be higher than the ecl argument if it can be done without increasing the version.
* This function allows the user to create a custom sequence of segments that switches
* between modes (such as alphanumeric and byte) to encode text in less space.
* This is a low-level API; the high-level API is qrcodegen_encodeText() and qrcodegen_encodeBinary().
* To save memory, the segments' data buffers can alias/overlap tempBuffer, and will
* result in them being clobbered, but the QR Code output will still be correct.
* But the qrcode array must not overlap tempBuffer or any segment's data buffer.
*/
bool qrcodegen_encodeSegments(const struct qrcodegen_Segment segs[], size_t len,
enum qrcodegen_Ecc ecl, uint8_t tempBuffer[], uint8_t qrcode[]);
/*
* Renders a QR Code representing the given segments with the given encoding parameters.
* Returns true if QR Code creation succeeded, or false if the data is too long to fit in the range of versions.
* The smallest possible QR Code version within the given range is automatically
* chosen for the output. Iff boostEcl is true, then the ECC level of the result
* may be higher than the ecl argument if it can be done without increasing the
* version. The mask is either between qrcodegen_Mask_0 to 7 to force that mask, or
* qrcodegen_Mask_AUTO to automatically choose an appropriate mask (which may be slow).
* This function allows the user to create a custom sequence of segments that switches
* between modes (such as alphanumeric and byte) to encode text in less space.
* This is a low-level API; the high-level API is qrcodegen_encodeText() and qrcodegen_encodeBinary().
* To save memory, the segments' data buffers can alias/overlap tempBuffer, and will
* result in them being clobbered, but the QR Code output will still be correct.
* But the qrcode array must not overlap tempBuffer or any segment's data buffer.
*/
bool qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[], size_t len, enum qrcodegen_Ecc ecl,
int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl, uint8_t tempBuffer[], uint8_t qrcode[]);
/*
* Tests whether the given string can be encoded as a segment in alphanumeric mode.
* A string is encodable iff each character is in the following set: 0 to 9, A to Z
* (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon.
*/
bool qrcodegen_isAlphanumeric(const char *text);
/*
* Tests whether the given string can be encoded as a segment in numeric mode.
* A string is encodable iff each character is in the range 0 to 9.
*/
bool qrcodegen_isNumeric(const char *text);
/*
* Returns the number of bytes (uint8_t) needed for the data buffer of a segment
* containing the given number of characters using the given mode. Notes:
* - Returns SIZE_MAX on failure, i.e. numChars > INT16_MAX or
* the number of needed bits exceeds INT16_MAX (i.e. 32767).
* - Otherwise, all valid results are in the range [0, ceil(INT16_MAX / 8)], i.e. at most 4096.
* - It is okay for the user to allocate more bytes for the buffer than needed.
* - For byte mode, numChars measures the number of bytes, not Unicode code points.
* - For ECI mode, numChars must be 0, and the worst-case number of bytes is returned.
* An actual ECI segment can have shorter data. For non-ECI modes, the result is exact.
*/
size_t qrcodegen_calcSegmentBufferSize(enum qrcodegen_Mode mode, size_t numChars);
/*
* Returns a segment representing the given binary data encoded in
* byte mode. All input byte arrays are acceptable. Any text string
* can be converted to UTF-8 bytes and encoded as a byte mode segment.
*/
struct qrcodegen_Segment qrcodegen_makeBytes(const uint8_t data[], size_t len, uint8_t buf[]);
/*
* Returns a segment representing the given string of decimal digits encoded in numeric mode.
*/
struct qrcodegen_Segment qrcodegen_makeNumeric(const char *digits, uint8_t buf[]);
/*
* Returns a segment representing the given text string encoded in alphanumeric mode.
* The characters allowed are: 0 to 9, A to Z (uppercase only), space,
* dollar, percent, asterisk, plus, hyphen, period, slash, colon.
*/
struct qrcodegen_Segment qrcodegen_makeAlphanumeric(const char *text, uint8_t buf[]);
/*
* Returns a segment representing an Extended Channel Interpretation
* (ECI) designator with the given assignment value.
*/
struct qrcodegen_Segment qrcodegen_makeEci(long assignVal, uint8_t buf[]);
/*---- Functions to extract raw data from QR Codes ----*/
/*
* Returns the side length of the given QR Code, assuming that encoding succeeded.
* The result is in the range [21, 177]. Note that the length of the array buffer
* is related to the side length - every 'uint8_t qrcode[]' must have length at least
* qrcodegen_BUFFER_LEN_FOR_VERSION(version), which equals ceil(size^2 / 8 + 1).
*/
int qrcodegen_getSize(const uint8_t qrcode[]);
/*
* Returns the color of the module (pixel) at the given coordinates, which is false
* for white or true for black. The top left corner has the coordinates (x=0, y=0).
* If the given coordinates are out of bounds, then false (white) is returned.
*/
bool qrcodegen_getModule(const uint8_t qrcode[], int x, int y);
#ifdef __cplusplus
}
#endif

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More