diff --git a/.codespellrc b/.codespellrc index ef27af48047..9c9720e3ebe 100644 --- a/.codespellrc +++ b/.codespellrc @@ -1,4 +1,4 @@ [codespell] -skip = build,*.yuv,components/fatfs/src/*,alice.txt,*.rgb,components/wpa_supplicant/*,components/esp_wifi/*,*.pem,*/COPYING*,docs/sphinx-known-warnings.txt +skip = build,*.yuv,components/fatfs/src/*,alice.txt,*.rgb,*.ppm,components/wpa_supplicant/*,components/esp_wifi/*,*.pem,*/COPYING*,docs/sphinx-known-warnings.txt ignore-words-list = ser,dout,rsource,fram,inout,shs,ans,aci,unstall,unstalling,hart,wheight,wel,ot,fane,assertIn,registr,oen,parms,aCount write-changes = true diff --git a/docs/en/api-reference/peripherals/ppa.rst b/docs/en/api-reference/peripherals/ppa.rst index 9414289b5bf..8ed24ba8af1 100644 --- a/docs/en/api-reference/peripherals/ppa.rst +++ b/docs/en/api-reference/peripherals/ppa.rst @@ -175,8 +175,9 @@ The PPA operations are acted on the target block of an input picture. Therefore, Application Examples ^^^^^^^^^^^^^^^^^^^^ -* :example:`peripherals/ppa/ppa_transform` - PPA transform image processing example. The embedded RGB565 image is transformed by SRM, highlighted with blend, framed with fill, and emitted as base64 for host-side PPM reconstruction and golden-image comparison. +* :example:`peripherals/ppa/ppa_transform` - PPA transform image processing example. The embedded RGB565 image is transformed by SRM, framed with fill, and emitted as base64 for host-side PPM reconstruction and golden-image comparison. * :example:`peripherals/ppa/ppa_color_key` - PPA blend color-keying example. The example generates a centered RGB888 glow foreground in software, then demonstrates two blend effects on the embedded RGB565 image: replacing the keyed red `ESP32` text with the glow, and preserving the keyed text while blending the glow into the non-key area. Both results are emitted as base64 for host-side PPM reconstruction and golden-image comparison. +* :example:`peripherals/ppa/ppa_freetype` - PPA FreeType icon compositing example. The example uses the `espressif/freetype` component to rasterize Font Awesome icon code points (two rows of three) into small per-glyph A8 alpha masks, then uses the PPA blend engine to composite each glyph over a software-generated gradient with a fixed icon color. The final composited RGB565 frame is emitted as base64 for host-side PPM reconstruction and golden-image comparison. API Reference ------------- diff --git a/docs/zh_CN/api-reference/peripherals/ppa.rst b/docs/zh_CN/api-reference/peripherals/ppa.rst index 58f4001603f..9b62cb26c7d 100644 --- a/docs/zh_CN/api-reference/peripherals/ppa.rst +++ b/docs/zh_CN/api-reference/peripherals/ppa.rst @@ -175,8 +175,9 @@ PPA 操作作用于输入图片的目标块。因此,完成一次 PPA 事务 应用示例 ^^^^^^^^^ -* :example:`peripherals/ppa/ppa_transform` - PPA transform 图像处理示例。嵌入的 RGB565 图像会经过 SRM 变换、blend 高亮和 fill 边框处理,然后以 base64 输出,供主机端重建为 PPM 并与 golden 图像比对。 +* :example:`peripherals/ppa/ppa_transform` - PPA transform 图像处理示例。嵌入的 RGB565 图像会经过 SRM 变换和 fill 边框处理,然后以 base64 输出,供主机端重建为 PPM 并与 golden 图像比对。 * :example:`peripherals/ppa/ppa_color_key` - PPA blend color key 示例。示例先在软件中生成居中的 RGB888 glow 前景,然后在嵌入式 RGB565 图片上演示两种 blend 效果:一种是通过 blend color key 将命中的红色 `ESP32` 文本像素替换为 glow,另一种是在保留命中文本的同时,将 glow 混合到非 key 区域。两种结果都会以 base64 输出,供主机端重建为 PPM 并与 golden 图像比对。 +* :example:`peripherals/ppa/ppa_freetype` - PPA FreeType 图标合成示例。示例使用 `espressif/freetype` 组件将 Font Awesome 图标的 Unicode 码点(两行,每行三个)栅格化为小的逐字形 A8 透明度掩码,然后通过 PPA blend 引擎以固定图标颜色将每个字形合成到软件生成的渐变背景上。最终合成后的 RGB565 帧会以 base64 输出,供主机端重建为 PPM 并与 golden 图像比对。 API 参考 -------- diff --git a/examples/peripherals/.build-test-rules.yml b/examples/peripherals/.build-test-rules.yml index 90f9bb4e7a6..c614127e9fd 100644 --- a/examples/peripherals/.build-test-rules.yml +++ b/examples/peripherals/.build-test-rules.yml @@ -528,6 +528,16 @@ examples/peripherals/ppa/ppa_color_key: - mbedtls - soc +examples/peripherals/ppa/ppa_freetype: + disable: + - if: SOC_PPA_SUPPORTED != 1 + depends_components: + - esp_driver_dma + - esp_driver_ppa + - esp_hal_ppa + - mbedtls + - soc + examples/peripherals/ppa/ppa_transform: disable: - if: SOC_PPA_SUPPORTED != 1 diff --git a/examples/peripherals/jpeg/jpeg_encode/sdkconfig.defaults b/examples/peripherals/jpeg/jpeg_encode/sdkconfig.defaults index 87d024ce27f..01fa5c7f82f 100644 --- a/examples/peripherals/jpeg/jpeg_encode/sdkconfig.defaults +++ b/examples/peripherals/jpeg/jpeg_encode/sdkconfig.defaults @@ -1,6 +1,5 @@ CONFIG_SPIRAM=y CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" -CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y CONFIG_ESPTOOLPY_FLASHSIZE="4MB" diff --git a/examples/peripherals/ppa/ppa_freetype/CMakeLists.txt b/examples/peripherals/ppa/ppa_freetype/CMakeLists.txt new file mode 100644 index 00000000000..c2c20aef474 --- /dev/null +++ b/examples/peripherals/ppa/ppa_freetype/CMakeLists.txt @@ -0,0 +1,8 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.22) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +idf_build_set_property(MINIMAL_BUILD ON) +project(ppa_freetype) diff --git a/examples/peripherals/ppa/ppa_freetype/README.md b/examples/peripherals/ppa/ppa_freetype/README.md new file mode 100644 index 00000000000..4aa0f273bf4 --- /dev/null +++ b/examples/peripherals/ppa/ppa_freetype/README.md @@ -0,0 +1,97 @@ +| Supported Targets | ESP32-P4 | ESP32-S31 | +| ----------------- | -------- | --------- | + +# PPA FreeType Example + +## Overview + +This example demonstrates how to render [Font Awesome](https://fontawesome.com/) icons with the FreeType library and composite them over a picture with the PPA blend engine, all without any display hardware. + +The example uses the `espressif/freetype` component (declared in `main/idf_component.yml`) to rasterize two rows of three icons: + +1. `star` (U+F005), `heart` (U+F004), `sun` (U+F185) +2. `face-smile` (U+F118), `bell` (U+F0F3), `envelope` (U+F0E0) + +It then generates a vertical gradient background in software, and lets the PPA blend engine composite every glyph over the gradient with a fixed white text color, one icon per `ppa_do_blend` call. The final RGB565 overlay result is base64-encoded and printed to the serial console. The accompanying pytest script reconstructs the composite as a color PPM and compares it with the golden reference image. + +The processing pipeline demonstrates: + +- FreeType: initialize the library, load an OTF font from a LittleFS partition, and rasterize each glyph into a small per-glyph A8 alpha mask +- Blend: call `ppa_do_blend` once per glyph, moving the blend target region (block) to each icon's bounding box and compositing the A8 glyph mask over the gradient using `fg_fix_rgb_val` as the icon color + +Blending one character at a time avoids building a full-frame A8 mask in software and allocating a large intermediate mask buffer, and it shows a multi-blend loop that moves the target region for every glyph. + +## Hardware Required + +* An ESP development board with PPA support +* An USB cable for power supply and programming + +## Build and Flash + +Run `idf.py -p PORT build flash monitor` to build, flash and monitor the project. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +```text +I (1555) main_task: Calling app_main() +LittleFS mounted +FreeType library initialized +Font loaded +Compositing text with PPA blend... +Rendering icon row at baseline Y=120: U+F005 U+F004 U+F185 +Rendering icon row at baseline Y=180: U+F118 U+F0F3 U+F0E0 +IMAGE_META effect=text_overlay width=320 height=240 format=RGB565 encoding=base64 +IMAGE_BASE64_BEGIN +IMAGE_BASE64 ... +IMAGE_BASE64 ... +IMAGE_BASE64_END +PPA FreeType demo done. +I (10085) main_task: Returned from app_main() +``` + +## Pytest Visual Check + +The accompanying `pytest_ppa_freetype.py` script captures the `IMAGE_META` and `IMAGE_BASE64` output, reconstructs the composite image, and saves it as: + +- `dut.logdir/ppa_freetype_text_overlay.ppm` + +The test compares the reconstructed RGB888 image byte-for-byte with `golden_text_overlay.ppm`. This turns the example into a functional regression test and a visual artifact generator for CI logs. + +### Getting The PPM Result Locally + +If you want to inspect the processed image on your computer, first build the example for your target, then run pytest from the example directory with the matching target and serial port: + +```bash +pytest pytest_ppa_freetype.py --target esp32p4 --port PORT +``` + +Replace `esp32p4` with another supported target such as `esp32s31`, and set `PORT` to your board's serial device. + +`pytest-embedded` stores per-test logs under `$IDF_PATH/pytest-embedded/`. When the test finishes, pytest prints a log line similar to: + +```text +Saved PPA artifact to .../pytest-embedded//esp32p4.default.test_ppa_freetype/ppa_freetype_text_overlay.ppm +``` + +You can open the generated PPM file from that log directory with any image viewer to inspect the PPA composite output locally. + +## Changing The Icons Or Font + +The rendered icons, font size, baselines, and icon color are all plain `#define`s at the top of `main/ppa_freetype_example_main.c`: + +- `EXAMPLE_ICON_ROW1` / `EXAMPLE_ICON_ROW2` - the two rows of Font Awesome code points (3 per row) +- `EXAMPLE_ROW1_BASELINE_Y` / `EXAMPLE_ROW2_BASELINE_Y` - the baseline row of each row in the 320x240 frame +- `EXAMPLE_FONT_PIXEL_SIZE` - the pixel size passed to `FT_Set_Pixel_Sizes()` +- `EXAMPLE_FG_R/G/B` - the fixed icon color used by the blend engine + +### Font Source And Format + +The example displays Font Awesome 7.3.1 Free regular icons. Font Awesome ships its Free fonts in several flavours: WOFF2 (`webfonts/fa-regular-400.woff2`) and OpenType (`otfs/Font Awesome 7 Free-Regular-400.otf`). The `espressif/freetype` component is built without WOFF2/brotli support, so it cannot decode a WOFF2 file directly. The OpenType (OTF) flavour, however, is fully supported by FreeType. + +So instead of committing a font binary into the tree, `main/CMakeLists.txt` downloads the OTF variant from the Font Awesome 7.3.1 release at build time (`Font Awesome 7 Free-Regular-400.otf` from ) and packs it into the `fonts` LittleFS partition as `fa-regular-400.otf`. + +The font is cached in the build directory after its first successful download. If the download fails, CMake stops with the upstream error instead of producing an invalid LittleFS image. diff --git a/examples/peripherals/ppa/ppa_freetype/golden_text_overlay.ppm b/examples/peripherals/ppa/ppa_freetype/golden_text_overlay.ppm new file mode 100644 index 00000000000..c502baeb0ce --- /dev/null +++ b/examples/peripherals/ppa/ppa_freetype/golden_text_overlay.ppm @@ -0,0 +1,4 @@ +P6 +320 240 +255 +)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q)Q!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!M!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!I!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!E!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A!A<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888889U{9Y8888888888888888888888888888888888888888888888888888888888888888888888888888888888BY{9U8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888881UB]88888888888888888888888888888888888888888888888888888888888888888888888888888888JaBY888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888<<8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888889YJe88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888JeBa8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888B]Je8888888888888888888888888!AZqskJe<888888888888 +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_check.h" +#include "esp_heap_caps.h" +#include "esp_littlefs.h" +#include "esp_log.h" +#include "mbedtls/base64.h" +#include "driver/ppa.h" +#include "freetype/freetype.h" + +#define EXAMPLE_IMAGE_WIDTH 320 +#define EXAMPLE_IMAGE_HEIGHT 240 +#define EXAMPLE_RGB565_PIXEL_SIZE 2 +#define EXAMPLE_IMAGE_SIZE (EXAMPLE_IMAGE_WIDTH * EXAMPLE_IMAGE_HEIGHT * EXAMPLE_RGB565_PIXEL_SIZE) +#define EXAMPLE_BASE64_CHUNK_LEN 96 + +#define EXAMPLE_FONT_PATH "/fonts/fa-regular-400.otf" +#define EXAMPLE_FONT_PIXEL_SIZE 40 + +/* The final composited image shows Font Awesome icons arranged in two rows of + * three, so six icons in total. Each row is rendered on its own baseline row so + * they stack vertically. The code points below are the Font Awesome Free + * regular glyphs (fa-regular-400): star, heart, sun / face-smile, bell, envelope. */ +#define EXAMPLE_ICONS_PER_ROW 3 +#define EXAMPLE_ICON_ROWS 2 +#define EXAMPLE_ICON_ROW1 0xF005, 0xF004, 0xF185 /* star, heart, sun */ +#define EXAMPLE_ICON_ROW2 0xF118, 0xF0F3, 0xF0E0 /* face-smile, bell, envelope */ +#define EXAMPLE_ROW1_BASELINE_Y 120 +#define EXAMPLE_ROW2_BASELINE_Y 180 +#define EXAMPLE_FG_R 0xff +#define EXAMPLE_FG_G 0xff +#define EXAMPLE_FG_B 0xff + +/* The Font Awesome icons to display, one row per line. Each row holds three + * icons and is rendered on its own baseline so the two rows stack vertically. */ +static const uint32_t example_icon_rows[EXAMPLE_ICON_ROWS][EXAMPLE_ICONS_PER_ROW] = { + { EXAMPLE_ICON_ROW1 }, + { EXAMPLE_ICON_ROW2 }, +}; +static const int example_icon_baseline_y[EXAMPLE_ICON_ROWS] = { + EXAMPLE_ROW1_BASELINE_Y, + EXAMPLE_ROW2_BASELINE_Y, +}; + +static const char *TAG = "example"; + +static void print_base64_payload(const unsigned char *encoded, size_t encoded_len) +{ + printf("IMAGE_BASE64_BEGIN\n"); + size_t chunk_count = 0; + for (size_t offset = 0; offset < encoded_len; offset += EXAMPLE_BASE64_CHUNK_LEN) { + size_t chunk_len = encoded_len - offset; + if (chunk_len > EXAMPLE_BASE64_CHUNK_LEN) { + chunk_len = EXAMPLE_BASE64_CHUNK_LEN; + } + printf("IMAGE_BASE64 %.*s\n", (int)chunk_len, (const char *)&encoded[offset]); + chunk_count++; + if ((chunk_count % 16) == 0) { + /* The complete payload is large. Yield periodically so the serial + * monitor used by pytest can consume every line reliably. */ + vTaskDelay(1); + } + } + printf("IMAGE_BASE64_END\n"); +} + +static void encode_and_print_image(const char *effect_name, const char *pixel_format, + const uint8_t *image, size_t image_size) +{ + /* Binary image data is not safe to print directly on the serial console. + * Base64 turns it into ASCII that pytest can capture and decode. */ + size_t encoded_len = 0; + int ret = mbedtls_base64_encode(NULL, 0, &encoded_len, image, image_size); + ESP_ERROR_CHECK((ret == MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) ? ESP_OK : ESP_FAIL); + + unsigned char *encoded = calloc(encoded_len + 1, 1); + ESP_ERROR_CHECK(encoded ? ESP_OK : ESP_ERR_NO_MEM); + ESP_ERROR_CHECK(mbedtls_base64_encode(encoded, encoded_len + 1, &encoded_len, image, image_size) == 0 ? ESP_OK : ESP_FAIL); + + printf("IMAGE_META effect=%s width=%u height=%u format=%s encoding=base64\n", + effect_name, EXAMPLE_IMAGE_WIDTH, EXAMPLE_IMAGE_HEIGHT, pixel_format); + print_base64_payload(encoded, encoded_len); + free(encoded); +} + +/* Report one rendered icon row to the console so the serial log and pytest can + * verify the exact set of Font Awesome icons being drawn. */ +static void report_icon_row(const uint32_t *icons, int count, int baseline_y) +{ + printf("Rendering icon row at baseline Y=%d:", baseline_y); + for (int i = 0; i < count; i++) { + printf(" U+%04X", (unsigned int)icons[i]); + } + printf("\n"); +} + +/* Rasterize a single Font Awesome glyph and composite it over the output frame + * with one PPA blend transaction. Instead of building a full-frame A8 mask in + * software, this helper lets PPA blend one glyph at a time into its own target + * region (the glyph's bounding box), so no large intermediate mask buffer is + * needed and the example shows a multi-blend loop. */ +static void compose_glyph(ppa_client_handle_t ppa_blend_handle, FT_Face face, + uint32_t icon, int pen_x, int baseline_y, + void *image_buf) +{ + ESP_ERROR_CHECK(FT_Load_Char(face, icon, FT_LOAD_RENDER) == 0 ? ESP_OK : ESP_FAIL); + + FT_GlyphSlot slot = face->glyph; + uint32_t w = slot->bitmap.width; + uint32_t h = slot->bitmap.rows; + /* bitmap_left uses the same right-positive X axis as the framebuffer. */ + int dst_x = pen_x + slot->bitmap_left; + /* bitmap_top is measured upward from the baseline, while framebuffer Y + * increases downward, hence the subtraction. */ + int dst_y = baseline_y - slot->bitmap_top; + + /* A glyph may produce an empty bitmap (e.g. a space); nothing to blend. */ + if (w == 0 || h == 0 || dst_x < 0 || dst_y < 0 || + dst_x + (int)w > EXAMPLE_IMAGE_WIDTH || dst_y + (int)h > EXAMPLE_IMAGE_HEIGHT) { + return; + } + + /* PPA accepts any addressable input buffer, including FreeType's internal + * SRAM bitmap. Its pitch is the row stride in bytes; use it as pic_w so + * PPA advances to the correct row when FreeType adds row padding. Negative + * pitch denotes a bottom-up bitmap, which PPA cannot consume directly. */ + if (slot->bitmap.pitch <= 0 || (uint32_t)slot->bitmap.pitch < w) { + ESP_LOGE(TAG, "Unsupported glyph bitmap pitch %d for U+%04X", + slot->bitmap.pitch, (unsigned int)icon); + abort(); + } + uint32_t glyph_pitch = (uint32_t)slot->bitmap.pitch; + + /* Blend the single-glyph A8 mask over the current image, writing the result + * back in place into the glyph's own bounding box. The target region + * (block) is moved for every glyph by changing block_offset_x/y. */ + ppa_blend_oper_config_t blend_config = { + .in_bg = { + .buffer = image_buf, + .pic_w = EXAMPLE_IMAGE_WIDTH, + .pic_h = EXAMPLE_IMAGE_HEIGHT, + .block_w = w, + .block_h = h, + .block_offset_x = dst_x, + .block_offset_y = dst_y, + .blend_cm = PPA_BLEND_COLOR_MODE_RGB565, + }, + .in_fg = { + .buffer = slot->bitmap.buffer, + .pic_w = glyph_pitch, + .pic_h = h, + .block_w = w, + .block_h = h, + .block_offset_x = 0, + .block_offset_y = 0, + .blend_cm = PPA_BLEND_COLOR_MODE_A8, + }, + .out = { + .buffer = image_buf, + .buffer_size = EXAMPLE_IMAGE_SIZE, + .pic_w = EXAMPLE_IMAGE_WIDTH, + .pic_h = EXAMPLE_IMAGE_HEIGHT, + .block_offset_x = dst_x, + .block_offset_y = dst_y, + .blend_cm = PPA_BLEND_COLOR_MODE_RGB565, + }, + .bg_alpha_update_mode = PPA_ALPHA_NO_CHANGE, + .fg_alpha_update_mode = PPA_ALPHA_NO_CHANGE, + .fg_fix_rgb_val = { + .r = EXAMPLE_FG_R, + .g = EXAMPLE_FG_G, + .b = EXAMPLE_FG_B, + }, + .mode = PPA_TRANS_MODE_BLOCKING, + }; + + ESP_ERROR_CHECK(ppa_do_blend(ppa_blend_handle, &blend_config)); +} + +/* Render one row of Font Awesome icons. The icons are spaced evenly (using each + * glyph's advance) and the whole row is centered horizontally on the given + * baseline row. Each icon is composited by a separate ppa_do_blend call whose + * target region is updated to the corresponding glyph's bounding box. */ +static void render_icon_row(ppa_client_handle_t ppa_blend_handle, FT_Face face, + const uint32_t *icons, int count, int baseline_y, + void *image_buf) +{ + ESP_ERROR_CHECK(FT_Set_Pixel_Sizes(face, 0, EXAMPLE_FONT_PIXEL_SIZE) == 0 ? ESP_OK : ESP_FAIL); + + /* FreeType stores glyph advances in 26.6 fixed-point format (pixels × 64). + * Shift right by 6 to convert them to whole-pixel pen positions. */ + /* First pass: compute the total advance of the row so it can be centered. */ + int total_advance = 0; + for (int i = 0; i < count; i++) { + ESP_ERROR_CHECK(FT_Load_Char(face, icons[i], FT_LOAD_DEFAULT) == 0 ? ESP_OK : ESP_FAIL); + total_advance += face->glyph->advance.x >> 6; + } + int pen_x = (EXAMPLE_IMAGE_WIDTH - total_advance) / 2; + + /* Second pass: blend each icon of the row into its own target region. */ + for (int i = 0; i < count; i++) { + compose_glyph(ppa_blend_handle, face, icons[i], pen_x, baseline_y, image_buf); + pen_x += face->glyph->advance.x >> 6; + } +} + +static void prepare_gradient_background(uint8_t *bg_buf) +{ + /* Generate a vertical RGB565 gradient in software as the background. This + * demonstrates that PPA can composite a rendered glyph buffer over any + * user-produced picture. The top color is brighter and the bottom darker. */ + const uint32_t top_rgb[3] = { 40, 80, 180 }; + const uint32_t bot_rgb[3] = { 8, 16, 60 }; + + for (int y = 0; y < EXAMPLE_IMAGE_HEIGHT; y++) { + uint32_t t = (uint32_t)y * 255 / (EXAMPLE_IMAGE_HEIGHT - 1); + uint32_t r = (top_rgb[0] * (255 - t) + bot_rgb[0] * t) / 255; + uint32_t g = (top_rgb[1] * (255 - t) + bot_rgb[1] * t) / 255; + uint32_t b = (top_rgb[2] * (255 - t) + bot_rgb[2] * t) / 255; + uint16_t v = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3); + for (int x = 0; x < EXAMPLE_IMAGE_WIDTH; x++) { + bg_buf[(y * EXAMPLE_IMAGE_WIDTH + x) * 2] = v & 0xff; + bg_buf[(y * EXAMPLE_IMAGE_WIDTH + x) * 2 + 1] = (v >> 8) & 0xff; + } + } +} + +void app_main(void) +{ + /* Mount the LittleFS partition that stores the font. */ + esp_vfs_littlefs_conf_t littlefs_conf = { + .base_path = "/fonts", + .partition_label = "fonts", + .format_if_mount_failed = true, + }; + ESP_ERROR_CHECK(esp_vfs_littlefs_register(&littlefs_conf)); + printf("LittleFS mounted\n"); + + /* Initialize FreeType library and load the font. */ + FT_Library library = NULL; + FT_Face face = NULL; + ESP_ERROR_CHECK(FT_Init_FreeType(&library) == 0 ? ESP_OK : ESP_FAIL); + printf("FreeType library initialized\n"); + ESP_ERROR_CHECK(FT_New_Face(library, EXAMPLE_FONT_PATH, 0, &face) == 0 ? ESP_OK : ESP_FAIL); + printf("Font loaded\n"); + + /* Create a software gradient picture, then update it in place for every + * glyph. This preserves both the background outside glyph blocks and any + * previously composited glyphs that overlap a later glyph block. */ + uint8_t *result_buf = heap_caps_aligned_calloc(64, 1, EXAMPLE_IMAGE_SIZE, + MALLOC_CAP_DMA | MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + ESP_ERROR_CHECK(result_buf ? ESP_OK : ESP_ERR_NO_MEM); + prepare_gradient_background(result_buf); + + ppa_client_handle_t ppa_blend_handle = NULL; + ppa_client_config_t ppa_blend_config = { + .oper_type = PPA_OPERATION_BLEND, + .max_pending_trans_num = 1, + }; + ESP_ERROR_CHECK(ppa_register_client(&ppa_blend_config, &ppa_blend_handle)); + + printf("Compositing text with PPA blend...\n"); + /* Composite each Font Awesome icon with its own PPA blend transaction. Every + * glyph is rasterized into a small A8 mask and blended over the background + * at its own target region, so no full-frame A8 mask is required. */ + for (int row = 0; row < EXAMPLE_ICON_ROWS; row++) { + report_icon_row(example_icon_rows[row], EXAMPLE_ICONS_PER_ROW, example_icon_baseline_y[row]); + render_icon_row(ppa_blend_handle, face, example_icon_rows[row], EXAMPLE_ICONS_PER_ROW, + example_icon_baseline_y[row], result_buf); + } + + encode_and_print_image("text_overlay", "RGB565", result_buf, EXAMPLE_IMAGE_SIZE); + printf("PPA FreeType demo done.\n"); + + ESP_ERROR_CHECK(ppa_unregister_client(ppa_blend_handle)); + free(result_buf); + FT_Done_Face(face); + FT_Done_FreeType(library); + ESP_ERROR_CHECK(esp_vfs_littlefs_unregister("fonts")); +} diff --git a/examples/peripherals/ppa/ppa_freetype/partitions.csv b/examples/peripherals/ppa/ppa_freetype/partitions.csv new file mode 100644 index 00000000000..78f5b641182 --- /dev/null +++ b/examples/peripherals/ppa/ppa_freetype/partitions.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 1M, +fonts, data, littlefs, , 0xF0000, diff --git a/examples/peripherals/ppa/ppa_freetype/pytest_ppa_freetype.py b/examples/peripherals/ppa/ppa_freetype/pytest_ppa_freetype.py new file mode 100644 index 00000000000..d3379a63272 --- /dev/null +++ b/examples/peripherals/ppa/ppa_freetype/pytest_ppa_freetype.py @@ -0,0 +1,207 @@ +# SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 +import base64 +import hashlib +import logging +import re +from dataclasses import dataclass +from pathlib import Path + +import pytest +from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize +from pytest_embedded_idf.utils import soc_filtered_targets + +IMAGE_META_PATTERN = ( + r'IMAGE_META effect=(?P\w+) width=(?P\d+) height=(?P\d+) ' + r'format=(?P\w+) encoding=(?P\w+)' +) +IMAGE_META_RE = re.compile(IMAGE_META_PATTERN) +IMAGE_CHUNK_PATTERN = r'IMAGE_BASE64 (?P[A-Za-z0-9+/=]+)' +IMAGE_CHUNK_RE = re.compile(IMAGE_CHUNK_PATTERN) +TEXT_OVERLAY_EFFECT = 'text_overlay' +EXPECTED_PIXEL_FORMAT = 'RGB565' +EXPECTED_ENCODING = 'base64' +RGB565_BYTES_PER_PIXEL = 2 +RGB888_BYTES_PER_PIXEL = 3 +PPM_MAGIC = b'P6' +PPM_MAX_VALUE = b'255' +PPM_HEADER_RE = re.compile(rb'^P6\s+(?P\d+)\s+(?P\d+)\s+(?P\d+)\s') + + +@dataclass(frozen=True) +class ImageMetadata: + effect: str + width: int + height: int + pixel_format: str + encoding: str + + @property + def rgb565_size(self) -> int: + return self.width * self.height * RGB565_BYTES_PER_PIXEL + + +@dataclass(frozen=True) +class RgbImage: + width: int + height: int + pixels_rgb888: bytes + + def __post_init__(self) -> None: + expected_size = self.width * self.height * RGB888_BYTES_PER_PIXEL + if len(self.pixels_rgb888) != expected_size: + raise ValueError(f'Expected {expected_size} RGB bytes, got {len(self.pixels_rgb888)}') + + +def parse_image_metadata(meta_line: str) -> ImageMetadata: + match = IMAGE_META_RE.fullmatch(meta_line) + if not match: + raise ValueError(f'Invalid image metadata line: {meta_line}') + + return ImageMetadata( + effect=match.group('effect'), + width=int(match.group('width')), + height=int(match.group('height')), + pixel_format=match.group('format'), + encoding=match.group('encoding'), + ) + + +def collect_base64_payload(dut: Dut) -> list[str]: + payload_lines: list[str] = [] + while True: + match = dut.expect(rf'(?PIMAGE_BASE64_END|{IMAGE_CHUNK_PATTERN}\r?\n)') + line = match.group('line').decode('utf-8').strip() + if line == 'IMAGE_BASE64_END': + return payload_lines + + chunk_match = IMAGE_CHUNK_RE.fullmatch(line) + assert chunk_match is not None + payload_lines.append(chunk_match.group('payload')) + + +def _rgb565_to_rgb888(raw_bytes: bytes) -> bytes: + # PPM stores 8-bit RGB triplets, so expand each RGB565 pixel before writing the artifact. + rgb_bytes = bytearray(len(raw_bytes) // 2 * 3) + for pixel_index, offset in enumerate(range(0, len(raw_bytes), 2)): + pixel = raw_bytes[offset] | (raw_bytes[offset + 1] << 8) + red = (pixel >> 11) & 0x1F + green = (pixel >> 5) & 0x3F + blue = pixel & 0x1F + rgb_offset = pixel_index * 3 + rgb_bytes[rgb_offset : rgb_offset + 3] = ( + (red << 3) | (red >> 2), + (green << 2) | (green >> 4), + (blue << 3) | (blue >> 2), + ) + return bytes(rgb_bytes) + + +def _encode_ppm(image: RgbImage) -> bytes: + header = b'%s\n%d %d\n%s\n' % (PPM_MAGIC, image.width, image.height, PPM_MAX_VALUE) + return header + image.pixels_rgb888 + + +def _load_ppm(path: Path) -> RgbImage: + ppm_bytes = path.read_bytes() + header_match = PPM_HEADER_RE.match(ppm_bytes) + if not header_match: + raise ValueError('Invalid PPM header') + + width = int(header_match.group('width')) + height = int(header_match.group('height')) + max_value = header_match.group('max_value') + if width <= 0 or height <= 0: + raise ValueError('Unsupported PPM dimensions') + if max_value != PPM_MAX_VALUE: + raise ValueError(f'Unsupported PPM max value: {max_value.decode("ascii", errors="replace")}') + + pixel_data = ppm_bytes[header_match.end() :] + expected_size = width * height * RGB888_BYTES_PER_PIXEL + if len(pixel_data) != expected_size: + raise ValueError(f'Expected {expected_size} PPM pixel bytes, got {len(pixel_data)}') + + return RgbImage(width=width, height=height, pixels_rgb888=pixel_data) + + +def decode_rgb565_base64_image(metadata: ImageMetadata, payload_lines: list[str]) -> RgbImage: + if metadata.pixel_format != EXPECTED_PIXEL_FORMAT: + raise ValueError(f'Unsupported pixel format: {metadata.pixel_format}') + if metadata.encoding != EXPECTED_ENCODING: + raise ValueError(f'Unsupported payload encoding: {metadata.encoding}') + + raw_bytes = base64.b64decode(''.join(payload_lines), validate=True) + if len(raw_bytes) != metadata.rgb565_size: + raise ValueError(f'Expected {metadata.rgb565_size} decoded bytes, got {len(raw_bytes)}') + + return RgbImage(width=metadata.width, height=metadata.height, pixels_rgb888=_rgb565_to_rgb888(raw_bytes)) + + +def save_ppm_artifact(image: RgbImage, output_path: Path) -> None: + output_path.parent.mkdir(parents=True, exist_ok=True) + try: + output_path.write_bytes(_encode_ppm(image)) + except OSError: + logging.exception('Failed to save PPA artifact to %s', output_path) + return + + logging.info('Saved PPA artifact to %s', output_path) + + +def image_digest(image: RgbImage) -> str: + digest = hashlib.sha256() + digest.update(image.width.to_bytes(4, 'big')) + digest.update(image.height.to_bytes(4, 'big')) + digest.update(image.pixels_rgb888) + return digest.hexdigest() + + +def assert_image_matches_golden(result_image: RgbImage, golden_path: Path) -> None: + assert golden_path.is_file(), f'Golden image not found: {golden_path}' + golden_image = _load_ppm(golden_path) + + assert image_digest(result_image) == image_digest(golden_image), ( + f'Generated image does not match golden file: {golden_path.name}' + ) + + +def effect_output_name(effect: str) -> str: + return f'ppa_freetype_{effect}.ppm' + + +def effect_golden_name(effect: str) -> str: + return f'golden_{effect}.ppm' + + +def expect_and_check_image(dut: Dut, effect: str) -> None: + metadata_line = dut.expect(IMAGE_META_PATTERN).group(0).decode('utf-8') + metadata = parse_image_metadata(metadata_line) + assert metadata.effect == effect, f'Expected effect {effect}, got {metadata.effect}' + + dut.expect_exact('IMAGE_BASE64_BEGIN') + base64_chunks = collect_base64_payload(dut) + + result_image = decode_rgb565_base64_image(metadata, base64_chunks) + output_path = Path(dut.logdir) / effect_output_name(effect) + save_ppm_artifact(result_image, output_path) + + golden_path = Path(__file__).with_name(effect_golden_name(effect)) + assert_image_matches_golden(result_image, golden_path) + + +@pytest.mark.generic +@idf_parametrize('target', soc_filtered_targets('SOC_PPA_SUPPORTED == 1'), indirect=['target']) +def test_ppa_freetype(dut: Dut) -> None: + dut.expect_exact('LittleFS mounted') + dut.expect_exact('FreeType library initialized') + dut.expect_exact('Font loaded') + dut.expect_exact('Compositing text with PPA blend...') + # Row 1: star, heart, sun (U+F005, U+F004, U+F185). + dut.expect_exact('Rendering icon row at baseline Y=120: U+F005 U+F004 U+F185') + # Row 2: face-smile, bell, envelope (U+F118, U+F0F3, U+F0E0). + dut.expect_exact('Rendering icon row at baseline Y=180: U+F118 U+F0F3 U+F0E0') + + expect_and_check_image(dut, TEXT_OVERLAY_EFFECT) + + dut.expect_exact('PPA FreeType demo done.') diff --git a/examples/peripherals/ppa/ppa_freetype/sdkconfig.defaults b/examples/peripherals/ppa/ppa_freetype/sdkconfig.defaults new file mode 100644 index 00000000000..a9234cab581 --- /dev/null +++ b/examples/peripherals/ppa/ppa_freetype/sdkconfig.defaults @@ -0,0 +1,7 @@ +CONFIG_SPIRAM=y + +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" + +CONFIG_ESP_MAIN_TASK_STACK_SIZE=20000 +CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y diff --git a/examples/peripherals/ppa/ppa_transform/README.md b/examples/peripherals/ppa/ppa_transform/README.md index 75cf831bf5b..0ba71cc8fee 100644 --- a/examples/peripherals/ppa/ppa_transform/README.md +++ b/examples/peripherals/ppa/ppa_transform/README.md @@ -5,14 +5,13 @@ ## Overview -This example demonstrates how to use `esp_driver_ppa` to process an embedded RGB565 image without any display hardware. +This example demonstrates how to use `esp_driver_ppa` to transform and fill an embedded RGB565 image without any display hardware. -The example embeds a pre-generated raw RGB565 image, processes it with three PPA engines, then base64-encodes the final RGB565 buffer and prints it to the serial console. The accompanying pytest script reconstructs the image as a PPM file and compares it with a golden reference image. +The example embeds a pre-generated raw RGB565 image, processes it with two PPA engines, then base64-encodes the final RGB565 buffer and prints it to the serial console. The accompanying pytest script reconstructs the image as a PPM file and compares it with a golden reference image. The processing pipeline demonstrates: - SRM: rotate the full image by 180 degrees -- Blend: overlay a cyan A8-mask highlight using a fixed foreground color - Fill: draw an amber frame and blue corner markers ## Hardware Required @@ -35,7 +34,6 @@ I (1555) main_task: Calling app_main() Loading embedded RGB565 image from flash... Embedded raw image size: 153600 bytes Transforming image with PPA SRM... -Blending highlight with PPA blend... Drawing border with PPA fill... IMAGE_META width=320 height=240 format=RGB565 encoding=base64 IMAGE_BASE64_BEGIN diff --git a/examples/peripherals/ppa/ppa_transform/golden_result.ppm b/examples/peripherals/ppa/ppa_transform/golden_result.ppm index 6842b444b12..0ba1459a58e 100644 Binary files a/examples/peripherals/ppa/ppa_transform/golden_result.ppm and b/examples/peripherals/ppa/ppa_transform/golden_result.ppm differ diff --git a/examples/peripherals/ppa/ppa_transform/main/ppa_transform_example_main.c b/examples/peripherals/ppa/ppa_transform/main/ppa_transform_example_main.c index 97ed5ac462d..091aaca0174 100644 --- a/examples/peripherals/ppa/ppa_transform/main/ppa_transform_example_main.c +++ b/examples/peripherals/ppa/ppa_transform/main/ppa_transform_example_main.c @@ -18,10 +18,8 @@ #define EXAMPLE_IMAGE_HEIGHT 240 #define EXAMPLE_RGB565_PIXEL_SIZE 2 #define EXAMPLE_IMAGE_SIZE (EXAMPLE_IMAGE_WIDTH * EXAMPLE_IMAGE_HEIGHT * EXAMPLE_RGB565_PIXEL_SIZE) -#define EXAMPLE_ALPHA_MASK_SIZE (EXAMPLE_IMAGE_WIDTH * EXAMPLE_IMAGE_HEIGHT) #define EXAMPLE_BASE64_CHUNK_LEN 96 #define EXAMPLE_BORDER_WIDTH 8 -#define EXAMPLE_HIGHLIGHT_ALPHA 160 /* The raw RGB565 asset is embedded by target_add_binary_data() in CMakeLists.txt. * RENAME_TO "image_rgb" gives the linker symbols below. */ @@ -144,73 +142,6 @@ static void draw_border(ppa_client_handle_t ppa_fill_handle, void *buffer) fill_rect(ppa_fill_handle, buffer, EXAMPLE_IMAGE_WIDTH - 56, EXAMPLE_IMAGE_HEIGHT - 88, 16, 48, blue); } -static void prepare_alpha_mask(uint8_t *alpha_mask) -{ - /* The blend stage uses this A8 image as a foreground alpha mask. Pixels with - * alpha 0 keep the transformed background unchanged; semi-transparent pixels - * mix the fixed cyan foreground color with the background. */ - for (int y = 0; y < EXAMPLE_IMAGE_HEIGHT; y++) { - for (int x = 0; x < EXAMPLE_IMAGE_WIDTH; x++) { - const int dx = x - (EXAMPLE_IMAGE_WIDTH * 2 / 3); - const int dy = y - (EXAMPLE_IMAGE_HEIGHT / 2); - const int distance2 = dx * dx + dy * dy; - uint8_t alpha = (distance2 < 70 * 70) ? EXAMPLE_HIGHLIGHT_ALPHA : 0; - if (x > y && x < y + 80) { - alpha = EXAMPLE_HIGHLIGHT_ALPHA; - } - alpha_mask[y * EXAMPLE_IMAGE_WIDTH + x] = alpha; - } - } -} - -static void blend_highlight(ppa_client_handle_t ppa_blend_handle, const void *bg_buf, - const void *alpha_mask, void *out_buf) -{ - /* Use the transformed RGB565 image as the background and an A8 mask as the - * foreground. fg_fix_rgb_val provides the cyan color for masked pixels. */ - ppa_blend_oper_config_t blend_config = { - .in_bg = { - .buffer = bg_buf, - .pic_w = EXAMPLE_IMAGE_WIDTH, - .pic_h = EXAMPLE_IMAGE_HEIGHT, - .block_w = EXAMPLE_IMAGE_WIDTH, - .block_h = EXAMPLE_IMAGE_HEIGHT, - .block_offset_x = 0, - .block_offset_y = 0, - .blend_cm = PPA_BLEND_COLOR_MODE_RGB565, - }, - .in_fg = { - .buffer = alpha_mask, - .pic_w = EXAMPLE_IMAGE_WIDTH, - .pic_h = EXAMPLE_IMAGE_HEIGHT, - .block_w = EXAMPLE_IMAGE_WIDTH, - .block_h = EXAMPLE_IMAGE_HEIGHT, - .block_offset_x = 0, - .block_offset_y = 0, - .blend_cm = PPA_BLEND_COLOR_MODE_A8, - }, - .out = { - .buffer = out_buf, - .buffer_size = EXAMPLE_IMAGE_SIZE, - .pic_w = EXAMPLE_IMAGE_WIDTH, - .pic_h = EXAMPLE_IMAGE_HEIGHT, - .block_offset_x = 0, - .block_offset_y = 0, - .blend_cm = PPA_BLEND_COLOR_MODE_RGB565, - }, - .bg_alpha_update_mode = PPA_ALPHA_NO_CHANGE, - .fg_alpha_update_mode = PPA_ALPHA_NO_CHANGE, - .fg_fix_rgb_val = { - .r = 0x18, - .g = 0xf0, - .b = 0xff, - }, - .mode = PPA_TRANS_MODE_BLOCKING, - }; - - ESP_ERROR_CHECK(ppa_do_blend(ppa_blend_handle, &blend_config)); -} - static void encode_and_print_image(const uint8_t *image, size_t image_size) { /* Binary image data is not safe to print directly on the serial console. @@ -237,10 +168,6 @@ void app_main(void) assert(embedded_size == EXAMPLE_IMAGE_SIZE); uint8_t *srm_buf = alloc_ppa_buffer(EXAMPLE_IMAGE_SIZE); - uint8_t *result_buf = alloc_ppa_buffer(EXAMPLE_IMAGE_SIZE); - uint8_t *alpha_mask = alloc_ppa_buffer(EXAMPLE_ALPHA_MASK_SIZE); - - prepare_alpha_mask(alpha_mask); ppa_client_handle_t ppa_srm_handle = NULL; ppa_client_config_t ppa_srm_config = { @@ -249,13 +176,6 @@ void app_main(void) }; ESP_ERROR_CHECK(ppa_register_client(&ppa_srm_config, &ppa_srm_handle)); - ppa_client_handle_t ppa_blend_handle = NULL; - ppa_client_config_t ppa_blend_config = { - .oper_type = PPA_OPERATION_BLEND, - .max_pending_trans_num = 1, - }; - ESP_ERROR_CHECK(ppa_register_client(&ppa_blend_config, &ppa_blend_handle)); - ppa_client_handle_t ppa_fill_handle = NULL; ppa_client_config_t ppa_fill_config = { .oper_type = PPA_OPERATION_FILL, @@ -263,28 +183,21 @@ void app_main(void) }; ESP_ERROR_CHECK(ppa_register_client(&ppa_fill_config, &ppa_fill_handle)); - /* The three steps below are deliberately simple and independent: + /* The two steps below are deliberately simple and independent: * 1. SRM transforms the source image into srm_buf. - * 2. Blend overlays a highlighted shape into result_buf. - * 3. Fill draws visible markers directly into result_buf. */ + * 2. Fill draws visible markers directly into srm_buf. */ printf("Transforming image with PPA SRM...\n"); /* PPA is backed by DMA2D, which can read the source image directly from * mapped flash. Only the destination buffers need DMA-capable RAM here. */ transform_image(ppa_srm_handle, image_rgb_start, srm_buf); - printf("Blending highlight with PPA blend...\n"); - blend_highlight(ppa_blend_handle, srm_buf, alpha_mask, result_buf); - printf("Drawing border with PPA fill...\n"); - draw_border(ppa_fill_handle, result_buf); + draw_border(ppa_fill_handle, srm_buf); - encode_and_print_image(result_buf, EXAMPLE_IMAGE_SIZE); + encode_and_print_image(srm_buf, EXAMPLE_IMAGE_SIZE); printf("PPA image processing demo done.\n"); ESP_ERROR_CHECK(ppa_unregister_client(ppa_srm_handle)); - ESP_ERROR_CHECK(ppa_unregister_client(ppa_blend_handle)); ESP_ERROR_CHECK(ppa_unregister_client(ppa_fill_handle)); free(srm_buf); - free(result_buf); - free(alpha_mask); } diff --git a/examples/peripherals/ppa/ppa_transform/pytest_ppa_transform.py b/examples/peripherals/ppa/ppa_transform/pytest_ppa_transform.py index 2cec4ecb0e2..010e0e4667d 100644 --- a/examples/peripherals/ppa/ppa_transform/pytest_ppa_transform.py +++ b/examples/peripherals/ppa/ppa_transform/pytest_ppa_transform.py @@ -170,7 +170,6 @@ def test_ppa_transform(dut: Dut) -> None: dut.expect_exact('Loading embedded RGB565 image from flash...') dut.expect(r'Embedded raw image size: \d+ bytes') dut.expect_exact('Transforming image with PPA SRM...') - dut.expect_exact('Blending highlight with PPA blend...') dut.expect_exact('Drawing border with PPA fill...') metadata_line = dut.expect(IMAGE_META_PATTERN).group(0).decode('utf-8')