diff --git a/components/platform/CMakeLists.txt b/components/platform/CMakeLists.txt index f0e8899..bd13e81 100644 --- a/components/platform/CMakeLists.txt +++ b/components/platform/CMakeLists.txt @@ -84,12 +84,18 @@ set_source_files_properties( ) set(platform_audio_prompt_src_dir "${CMAKE_CURRENT_SOURCE_DIR}/assets/audio_prompts") -set(platform_lcd_asset_src_dir "${CMAKE_CURRENT_SOURCE_DIR}/assets/lcd") +set(platform_lcd_asset_source_dir "${CMAKE_CURRENT_SOURCE_DIR}/assets/lcd_source") +set(platform_lcd_asset_src_dir "${CMAKE_CURRENT_SOURCE_DIR}/assets/lcd_optimized") +set(platform_lcd_asset_manifest "${platform_lcd_asset_src_dir}/manifest.json") +set(platform_lcd_asset_optimizer "${CMAKE_CURRENT_SOURCE_DIR}/../../tools/optimize_lcd_assets.py") set(platform_asset_image_dir "${CMAKE_CURRENT_BINARY_DIR}/asset_image") set(platform_asset_stage_stamp "${CMAKE_CURRENT_BINARY_DIR}/asset_image.stamp") file(GLOB_RECURSE platform_audio_prompt_files CONFIGURE_DEPENDS "${platform_audio_prompt_src_dir}/*.opus" ) +file(GLOB platform_lcd_source_png_files CONFIGURE_DEPENDS + "${platform_lcd_asset_source_dir}/*.png" +) set(platform_lcd_png_assets buyunqian1 jichubiaoqing0 @@ -179,13 +185,17 @@ endforeach() add_custom_command( OUTPUT "${platform_asset_stage_stamp}" + COMMAND ${PYTHON} "${platform_lcd_asset_optimizer}" check-current COMMAND ${CMAKE_COMMAND} -E rm -rf "${platform_asset_image_dir}" COMMAND ${CMAKE_COMMAND} -E make_directory "${platform_asset_image_dir}" ${platform_asset_stage_commands} COMMAND ${CMAKE_COMMAND} -E touch "${platform_asset_stage_stamp}" DEPENDS ${platform_audio_prompt_files} + ${platform_lcd_source_png_files} ${platform_lcd_png_asset_files} + "${platform_lcd_asset_manifest}" + "${platform_lcd_asset_optimizer}" ) littlefs_create_partition_image( diff --git a/components/platform/assets/lcd_optimized/buyunqian1.png b/components/platform/assets/lcd_optimized/buyunqian1.png new file mode 100644 index 0000000..2ff4121 Binary files /dev/null and b/components/platform/assets/lcd_optimized/buyunqian1.png differ diff --git a/components/platform/assets/lcd_optimized/charging.png b/components/platform/assets/lcd_optimized/charging.png new file mode 100644 index 0000000..3a89078 Binary files /dev/null and b/components/platform/assets/lcd_optimized/charging.png differ diff --git a/components/platform/assets/lcd_optimized/chouqian_buyun0.png b/components/platform/assets/lcd_optimized/chouqian_buyun0.png new file mode 100644 index 0000000..a034ac6 Binary files /dev/null and b/components/platform/assets/lcd_optimized/chouqian_buyun0.png differ diff --git a/components/platform/assets/lcd_optimized/chouqian_buyun1.png b/components/platform/assets/lcd_optimized/chouqian_buyun1.png new file mode 100644 index 0000000..2ff4121 Binary files /dev/null and b/components/platform/assets/lcd_optimized/chouqian_buyun1.png differ diff --git a/components/platform/assets/lcd_optimized/chouqian_buyun2.png b/components/platform/assets/lcd_optimized/chouqian_buyun2.png new file mode 100644 index 0000000..01287e7 Binary files /dev/null and b/components/platform/assets/lcd_optimized/chouqian_buyun2.png differ diff --git a/components/platform/assets/lcd_optimized/chouqian_heping0.png b/components/platform/assets/lcd_optimized/chouqian_heping0.png new file mode 100644 index 0000000..5597580 Binary files /dev/null and b/components/platform/assets/lcd_optimized/chouqian_heping0.png differ diff --git a/components/platform/assets/lcd_optimized/chouqian_heping1.png b/components/platform/assets/lcd_optimized/chouqian_heping1.png new file mode 100644 index 0000000..9112873 Binary files /dev/null and b/components/platform/assets/lcd_optimized/chouqian_heping1.png differ diff --git a/components/platform/assets/lcd_optimized/chouqian_heping2.png b/components/platform/assets/lcd_optimized/chouqian_heping2.png new file mode 100644 index 0000000..d41788b Binary files /dev/null and b/components/platform/assets/lcd_optimized/chouqian_heping2.png differ diff --git a/components/platform/assets/lcd_optimized/chouqian_qianzhi0.png b/components/platform/assets/lcd_optimized/chouqian_qianzhi0.png new file mode 100644 index 0000000..6e10a15 Binary files /dev/null and b/components/platform/assets/lcd_optimized/chouqian_qianzhi0.png differ diff --git a/components/platform/assets/lcd_optimized/chouqian_qianzhi1.png b/components/platform/assets/lcd_optimized/chouqian_qianzhi1.png new file mode 100644 index 0000000..15615a9 Binary files /dev/null and b/components/platform/assets/lcd_optimized/chouqian_qianzhi1.png differ diff --git a/components/platform/assets/lcd_optimized/chouqian_qianzhi2.png b/components/platform/assets/lcd_optimized/chouqian_qianzhi2.png new file mode 100644 index 0000000..36e074f Binary files /dev/null and b/components/platform/assets/lcd_optimized/chouqian_qianzhi2.png differ diff --git a/components/platform/assets/lcd_optimized/chouqian_shangshang0.png b/components/platform/assets/lcd_optimized/chouqian_shangshang0.png new file mode 100644 index 0000000..73daac2 Binary files /dev/null and b/components/platform/assets/lcd_optimized/chouqian_shangshang0.png differ diff --git a/components/platform/assets/lcd_optimized/chouqian_shangshang1.png b/components/platform/assets/lcd_optimized/chouqian_shangshang1.png new file mode 100644 index 0000000..a7052f6 Binary files /dev/null and b/components/platform/assets/lcd_optimized/chouqian_shangshang1.png differ diff --git a/components/platform/assets/lcd_optimized/chouqian_shangshang2.png b/components/platform/assets/lcd_optimized/chouqian_shangshang2.png new file mode 100644 index 0000000..ca43b9a Binary files /dev/null and b/components/platform/assets/lcd_optimized/chouqian_shangshang2.png differ diff --git a/components/platform/assets/lcd_optimized/chouqian_xiaoji0.png b/components/platform/assets/lcd_optimized/chouqian_xiaoji0.png new file mode 100644 index 0000000..a59b6dd Binary files /dev/null and b/components/platform/assets/lcd_optimized/chouqian_xiaoji0.png differ diff --git a/components/platform/assets/lcd_optimized/chouqian_xiaoji1.png b/components/platform/assets/lcd_optimized/chouqian_xiaoji1.png new file mode 100644 index 0000000..355bd8a Binary files /dev/null and b/components/platform/assets/lcd_optimized/chouqian_xiaoji1.png differ diff --git a/components/platform/assets/lcd_optimized/chouqian_xiaoji2.png b/components/platform/assets/lcd_optimized/chouqian_xiaoji2.png new file mode 100644 index 0000000..3ecb593 Binary files /dev/null and b/components/platform/assets/lcd_optimized/chouqian_xiaoji2.png differ diff --git a/components/platform/assets/lcd_optimized/chouqian_zhongshang0.png b/components/platform/assets/lcd_optimized/chouqian_zhongshang0.png new file mode 100644 index 0000000..a23c02e Binary files /dev/null and b/components/platform/assets/lcd_optimized/chouqian_zhongshang0.png differ diff --git a/components/platform/assets/lcd_optimized/chouqian_zhongshang1.png b/components/platform/assets/lcd_optimized/chouqian_zhongshang1.png new file mode 100644 index 0000000..1a6cf2b Binary files /dev/null and b/components/platform/assets/lcd_optimized/chouqian_zhongshang1.png differ diff --git a/components/platform/assets/lcd_optimized/chouqian_zhongshang2.png b/components/platform/assets/lcd_optimized/chouqian_zhongshang2.png new file mode 100644 index 0000000..6a157a4 Binary files /dev/null and b/components/platform/assets/lcd_optimized/chouqian_zhongshang2.png differ diff --git a/components/platform/assets/lcd_optimized/east0.png b/components/platform/assets/lcd_optimized/east0.png new file mode 100644 index 0000000..24af787 Binary files /dev/null and b/components/platform/assets/lcd_optimized/east0.png differ diff --git a/components/platform/assets/lcd_optimized/huo0.png b/components/platform/assets/lcd_optimized/huo0.png new file mode 100644 index 0000000..f50f903 Binary files /dev/null and b/components/platform/assets/lcd_optimized/huo0.png differ diff --git a/components/platform/assets/lcd_optimized/huo1.png b/components/platform/assets/lcd_optimized/huo1.png new file mode 100644 index 0000000..12f3712 Binary files /dev/null and b/components/platform/assets/lcd_optimized/huo1.png differ diff --git a/components/platform/assets/lcd_optimized/icon_huo0.png b/components/platform/assets/lcd_optimized/icon_huo0.png new file mode 100644 index 0000000..8a1a8a0 Binary files /dev/null and b/components/platform/assets/lcd_optimized/icon_huo0.png differ diff --git a/components/platform/assets/lcd_optimized/icon_jin0.png b/components/platform/assets/lcd_optimized/icon_jin0.png new file mode 100644 index 0000000..2a00324 Binary files /dev/null and b/components/platform/assets/lcd_optimized/icon_jin0.png differ diff --git a/components/platform/assets/lcd_optimized/icon_mu0.png b/components/platform/assets/lcd_optimized/icon_mu0.png new file mode 100644 index 0000000..7f2e784 Binary files /dev/null and b/components/platform/assets/lcd_optimized/icon_mu0.png differ diff --git a/components/platform/assets/lcd_optimized/icon_shui0.png b/components/platform/assets/lcd_optimized/icon_shui0.png new file mode 100644 index 0000000..766050b Binary files /dev/null and b/components/platform/assets/lcd_optimized/icon_shui0.png differ diff --git a/components/platform/assets/lcd_optimized/icon_tu0.png b/components/platform/assets/lcd_optimized/icon_tu0.png new file mode 100644 index 0000000..02305f2 Binary files /dev/null and b/components/platform/assets/lcd_optimized/icon_tu0.png differ diff --git a/components/platform/assets/lcd_optimized/jichubiaoqing0.png b/components/platform/assets/lcd_optimized/jichubiaoqing0.png new file mode 100644 index 0000000..c8d47fc Binary files /dev/null and b/components/platform/assets/lcd_optimized/jichubiaoqing0.png differ diff --git a/components/platform/assets/lcd_optimized/jichubiaoqing1.png b/components/platform/assets/lcd_optimized/jichubiaoqing1.png new file mode 100644 index 0000000..b8df6f3 Binary files /dev/null and b/components/platform/assets/lcd_optimized/jichubiaoqing1.png differ diff --git a/components/platform/assets/lcd_optimized/jichubiaoqing2.png b/components/platform/assets/lcd_optimized/jichubiaoqing2.png new file mode 100644 index 0000000..36a5778 Binary files /dev/null and b/components/platform/assets/lcd_optimized/jichubiaoqing2.png differ diff --git a/components/platform/assets/lcd_optimized/jin0.png b/components/platform/assets/lcd_optimized/jin0.png new file mode 100644 index 0000000..30390e4 Binary files /dev/null and b/components/platform/assets/lcd_optimized/jin0.png differ diff --git a/components/platform/assets/lcd_optimized/jin1.png b/components/platform/assets/lcd_optimized/jin1.png new file mode 100644 index 0000000..c6a7e11 Binary files /dev/null and b/components/platform/assets/lcd_optimized/jin1.png differ diff --git a/components/platform/assets/lcd_optimized/listening0.png b/components/platform/assets/lcd_optimized/listening0.png new file mode 100644 index 0000000..fc308ea Binary files /dev/null and b/components/platform/assets/lcd_optimized/listening0.png differ diff --git a/components/platform/assets/lcd_optimized/listening1.png b/components/platform/assets/lcd_optimized/listening1.png new file mode 100644 index 0000000..05b94b6 Binary files /dev/null and b/components/platform/assets/lcd_optimized/listening1.png differ diff --git a/components/platform/assets/lcd_optimized/listening2.png b/components/platform/assets/lcd_optimized/listening2.png new file mode 100644 index 0000000..7536a97 Binary files /dev/null and b/components/platform/assets/lcd_optimized/listening2.png differ diff --git a/components/platform/assets/lcd_optimized/lowbat.png b/components/platform/assets/lcd_optimized/lowbat.png new file mode 100644 index 0000000..857d78a Binary files /dev/null and b/components/platform/assets/lcd_optimized/lowbat.png differ diff --git a/components/platform/assets/lcd_optimized/manifest.json b/components/platform/assets/lcd_optimized/manifest.json new file mode 100644 index 0000000..8e7f20c --- /dev/null +++ b/components/platform/assets/lcd_optimized/manifest.json @@ -0,0 +1,897 @@ +{ + "assets": { + "buyunqian1.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 7598, + "optimized_sha256": "78c3ac6965021f7d2ecbec6150232930ecfb633564677ac38b25d7e13d408079", + "output_mode": "indexed", + "rgb565_colors": 172, + "source_bytes": 20467, + "source_mode": "RGBA", + "source_sha256": "4e5d7af42f94870cfcb714c83098b265eb9e0c4f44a0de6f667c24b5eeea84f1", + "width": 320 + }, + "charging.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 70, + "optimized_bytes": 1254, + "optimized_sha256": "e66a3d46c35f7af05f8029fa2e103694705c1831483ab4f99c232b7ad3fdd5c0", + "output_mode": "indexed", + "rgb565_colors": 53, + "source_bytes": 3018, + "source_mode": "RGBA", + "source_sha256": "5708c7634d08b9cd626772eeede3b32f6678c12d85da34106fad34b0247ed1d4", + "width": 50 + }, + "chouqian_buyun0.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 6536, + "optimized_sha256": "036c6fd38f2b955388a7f02e238cc659e7228fc149c16776723d9fa6332798a4", + "output_mode": "indexed", + "rgb565_colors": 124, + "source_bytes": 16883, + "source_mode": "RGBA", + "source_sha256": "ffe3f89e7453f0a9efaf3934d77fad8c1aee0521ad7ff2a8ab2bc762dbfe1d10", + "width": 320 + }, + "chouqian_buyun1.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 7598, + "optimized_sha256": "78c3ac6965021f7d2ecbec6150232930ecfb633564677ac38b25d7e13d408079", + "output_mode": "indexed", + "rgb565_colors": 172, + "source_bytes": 20467, + "source_mode": "RGBA", + "source_sha256": "4e5d7af42f94870cfcb714c83098b265eb9e0c4f44a0de6f667c24b5eeea84f1", + "width": 320 + }, + "chouqian_buyun2.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 9986, + "optimized_sha256": "29812ecf86309374ff5557a17606208e8a90dd2a7c1ca7771d534079f216a2c5", + "output_mode": "indexed", + "rgb565_colors": 189, + "source_bytes": 27971, + "source_mode": "RGBA", + "source_sha256": "6875f0f72daeac443e4ad55d5cebabe0ec1425f9f3922c85a99483239fdc1a1e", + "width": 320 + }, + "chouqian_heping0.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 6590, + "optimized_sha256": "3da1cd069c89d11f528d08fb48a2721c34230b8439d9de78b60d14f70f51a120", + "output_mode": "indexed", + "rgb565_colors": 121, + "source_bytes": 17146, + "source_mode": "RGBA", + "source_sha256": "88e91d60c577ce93219e5584212b4c4fb16d3284d98aca82e9de02e6b3f09452", + "width": 320 + }, + "chouqian_heping1.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 7701, + "optimized_sha256": "a67fc94963906a124d1d854f187890659ba857aca86c8616bdb59125faa86a1d", + "output_mode": "indexed", + "rgb565_colors": 160, + "source_bytes": 20975, + "source_mode": "RGBA", + "source_sha256": "c7db44e3fbf3524270c31a0e1c258db1d62fab2d92b60899e5c1dcd3123072b2", + "width": 320 + }, + "chouqian_heping2.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 10120, + "optimized_sha256": "e2c9c6c14b75273d6151f60a52c9fd9775c3a9a32534f8f27fe79bf4bab56b46", + "output_mode": "indexed", + "rgb565_colors": 182, + "source_bytes": 28152, + "source_mode": "RGBA", + "source_sha256": "b50b8ef8eabccfd829ab0f0d49384cdcb1d404184cb85bb94e5b14091f5d0b51", + "width": 320 + }, + "chouqian_qianzhi0.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 5203, + "optimized_sha256": "98df3895f97af4279db78c405f125592e1fcb6fb2fa108296a7b5f977bc672a8", + "output_mode": "indexed", + "rgb565_colors": 168, + "source_bytes": 13739, + "source_mode": "RGBA", + "source_sha256": "30d168dff2842b461df11b3423f7ddf0d2e1f3ed742f09490f137a6b64c5c7e3", + "width": 320 + }, + "chouqian_qianzhi1.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 6119, + "optimized_sha256": "4a5146c6f7e9e0b99176935f5e37fcd805de100d0915b7f44577651f5f6b1af9", + "output_mode": "indexed", + "rgb565_colors": 166, + "source_bytes": 16425, + "source_mode": "RGBA", + "source_sha256": "f822f5c7a3207cfefc3ad9fa586ce69d90d4f5336419f41f41b34426d42c54ad", + "width": 320 + }, + "chouqian_qianzhi2.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 5984, + "optimized_sha256": "d748d40bddbc55fa01d4d935942e7c87351d6f44b8a811f0218418932af3415d", + "output_mode": "indexed", + "rgb565_colors": 144, + "source_bytes": 15559, + "source_mode": "RGBA", + "source_sha256": "5f076a229978e5bc0d26ccadc7eff4dedfa4f0647c0a77c276ca58bbb0d89f1c", + "width": 320 + }, + "chouqian_shangshang0.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 6578, + "optimized_sha256": "591c889db662dec3c4e895586bede63026567f8e8d65f0c3c32426ca52714676", + "output_mode": "indexed", + "rgb565_colors": 130, + "source_bytes": 17377, + "source_mode": "RGBA", + "source_sha256": "3f1e05abb619b2b8f30ea78cfa2b352ed42f7e147e7bb96e11c8b91cc2201e9f", + "width": 320 + }, + "chouqian_shangshang1.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 7519, + "optimized_sha256": "250f9e36e789a92b6f97ac38f33c10f562b429c3f5fec82a3284031db9bb2d46", + "output_mode": "indexed", + "rgb565_colors": 163, + "source_bytes": 20582, + "source_mode": "RGBA", + "source_sha256": "7b56648a4e4ae49f111d7a0ca733f58a39fb3f089cd37c3621b3ea5682ae52a9", + "width": 320 + }, + "chouqian_shangshang2.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 9804, + "optimized_sha256": "c4f2ec63edfd62fa004191a51b147dd9db23e0fc1c0cbd552b1bf3f54f32eb6e", + "output_mode": "indexed", + "rgb565_colors": 182, + "source_bytes": 28046, + "source_mode": "RGBA", + "source_sha256": "8a9a91cce3745e616b567dc0210576646ce4ec1a3236dc81a563d88818d68e40", + "width": 320 + }, + "chouqian_xiaoji0.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 6447, + "optimized_sha256": "8143f4eff4056188edbe4187e380811bf8dfd336f9e2fbcf9a61119ebaf60b28", + "output_mode": "indexed", + "rgb565_colors": 127, + "source_bytes": 17071, + "source_mode": "RGBA", + "source_sha256": "c1393c246dda14f956d66a4a39522f04af75d0a8489331f59501dd0e7a954326", + "width": 320 + }, + "chouqian_xiaoji1.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 7424, + "optimized_sha256": "1bf64e736e0bbab4a73b31f87909edc518514b7cd58e96ddb274007ccdca7a47", + "output_mode": "indexed", + "rgb565_colors": 166, + "source_bytes": 20313, + "source_mode": "RGBA", + "source_sha256": "6cf908409a869374958d6742f4573ae5f28f52c0d202304e18cd9c1d74073e45", + "width": 320 + }, + "chouqian_xiaoji2.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 9715, + "optimized_sha256": "49032b5d3a4d0e8a552375a825653717319a1f89d68258168993379adb9be20d", + "output_mode": "indexed", + "rgb565_colors": 182, + "source_bytes": 27354, + "source_mode": "RGBA", + "source_sha256": "1cec4bade1535764fd8cf37b9e3c1d59bec749c81f49129dbb1c4bca0d54a788", + "width": 320 + }, + "chouqian_zhongshang0.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 6291, + "optimized_sha256": "9af4a76d4d192c565d854cd1403415d625611a438756d7e624b3a214cecaf933", + "output_mode": "indexed", + "rgb565_colors": 129, + "source_bytes": 16995, + "source_mode": "RGBA", + "source_sha256": "4404b10bd7610aaf79da34c9715c598aedf1a7fb8c293548935370ff2ffa7d77", + "width": 320 + }, + "chouqian_zhongshang1.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 7224, + "optimized_sha256": "99db4512f6094a929ee93aa43dc07f17344eeaa1acb46b544e428bec527299eb", + "output_mode": "indexed", + "rgb565_colors": 165, + "source_bytes": 19968, + "source_mode": "RGBA", + "source_sha256": "074a93faa2cc0ef590240291072a58c16a4887954ffc4fd2dc3bcdd0f02d15cc", + "width": 320 + }, + "chouqian_zhongshang2.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 9387, + "optimized_sha256": "19193b1925f3ceeed276123f28dd26a99723ee32dc4028702c271771b0e23acc", + "output_mode": "indexed", + "rgb565_colors": 179, + "source_bytes": 26989, + "source_mode": "RGBA", + "source_sha256": "b1b9862f814d7d0262446556ecb9f11479dcae12919b9c37f8759f46d78ac15f", + "width": 320 + }, + "east0.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 5681, + "optimized_sha256": "eec4396faec5ac72814b092b697f97380db83f1fbd30adbeb719dd429b9ae2ac", + "output_mode": "indexed", + "rgb565_colors": 212, + "source_bytes": 19981, + "source_mode": "RGBA", + "source_sha256": "fa107cc1b3674c1b1518eecd7f024e8861be69dbc45a3a24776b013f132f7c19", + "width": 320 + }, + "huo0.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 7023, + "optimized_sha256": "b2600e360afa78e8b0f15d9df7f3d07d96151b1942f8840134b3b0b9c91e7811", + "output_mode": "indexed", + "rgb565_colors": 164, + "source_bytes": 19143, + "source_mode": "RGBA", + "source_sha256": "a635c94c25f48c60a3e5dfdc945d46cfa39db104305369f72cafa1855637a185", + "width": 320 + }, + "huo1.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 7269, + "optimized_sha256": "44ba7fc83acbbef71cd55d823c8707a3d009aadd1ac819878b9cd4645a657b7a", + "output_mode": "indexed", + "rgb565_colors": 122, + "source_bytes": 19706, + "source_mode": "RGBA", + "source_sha256": "ffb043780e13f488c7f2540b3b40727fb6527ac0909d8fdce50371d6eb9c9e0d", + "width": 320 + }, + "icon_huo0.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 58, + "optimized_bytes": 1422, + "optimized_sha256": "425d9d5090ab125ea11d1516b53864834651b5e7eed647ed8379a6ecbe80f061", + "output_mode": "indexed", + "rgb565_colors": 55, + "source_bytes": 2028, + "source_mode": "RGBA", + "source_sha256": "4ca0d12b675dec5ab6d9378220318e3bb23aa6ef2adc037b1dede183726b9ea0", + "width": 56 + }, + "icon_jin0.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 44, + "optimized_bytes": 1509, + "optimized_sha256": "dd9c60874b0d89854782b7a37f647ba28b06b522b9271a1f3eb4f69fc1a1d944", + "output_mode": "indexed", + "rgb565_colors": 76, + "source_bytes": 2369, + "source_mode": "RGBA", + "source_sha256": "f69c7bcf28e4599ccd0c0dcc33bd4f2ce3072db2e5feb82a6bc9f17c309c8611", + "width": 65 + }, + "icon_mu0.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 50, + "optimized_bytes": 1866, + "optimized_sha256": "c7be2beef34eb8f65632688b428bb48f8e028dbf9e36dcb7111fd47242f35e65", + "output_mode": "indexed", + "rgb565_colors": 78, + "source_bytes": 3741, + "source_mode": "RGBA", + "source_sha256": "f0d31be201e91626ffc61436928f7c94d968cce15a34434b035f467e2238ccdb", + "width": 86 + }, + "icon_shui0.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 56, + "optimized_bytes": 1457, + "optimized_sha256": "06f93176d799461944f7d6549aa90ce887efcab2cf2c44e804d66adfa09433cf", + "output_mode": "indexed", + "rgb565_colors": 73, + "source_bytes": 2239, + "source_mode": "RGBA", + "source_sha256": "ab8cd173f81783969157f52f900f9586cc097aba1f8be2aa7348b648404bde1b", + "width": 60 + }, + "icon_tu0.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 42, + "optimized_bytes": 1643, + "optimized_sha256": "1339026c2c3119a8b2599fc95512375b2c833620e7c52bedebef2e838e803329", + "output_mode": "indexed", + "rgb565_colors": 76, + "source_bytes": 2873, + "source_mode": "RGBA", + "source_sha256": "06e4d90f11a1ba03dccf75dafa03b2dd8ebfda43ff1c8dcc19dcf6844a7527f9", + "width": 75 + }, + "jichubiaoqing0.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 6467, + "optimized_sha256": "e40259f02e37695142f2236afde75d38ff5859711289893e0f2de1cd298f2856", + "output_mode": "indexed", + "rgb565_colors": 240, + "source_bytes": 20645, + "source_mode": "RGBA", + "source_sha256": "a55a11b4661056bdcb5e50cd10cac118c77ff46eadd2f11a2e507e8a8884ea2e", + "width": 320 + }, + "jichubiaoqing1.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 5306, + "optimized_sha256": "54f75179021dfc7adc6495e88d797939d0af16b63c4fbd1b0092c1f0076b3134", + "output_mode": "indexed", + "rgb565_colors": 229, + "source_bytes": 16355, + "source_mode": "RGBA", + "source_sha256": "23d663e0b80d961d3d05455ed044cfd08a6a34fb50854c9c309e29aa25fb9459", + "width": 320 + }, + "jichubiaoqing2.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 4482, + "optimized_sha256": "9d28ce33e5a360c29a17f6d477c17d0c1bdd47b9bcf4e5b2777915f0efbb22ba", + "output_mode": "indexed", + "rgb565_colors": 218, + "source_bytes": 14340, + "source_mode": "RGBA", + "source_sha256": "95e0f20e890c5b587779ffa6ffdda13df917c5888e55907c6040ace172d62161", + "width": 320 + }, + "jin0.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 6488, + "optimized_sha256": "9c6fed3dc5b4617e5cab35024a9f29573bd5913d7a45d1a981a3187b934db5aa", + "output_mode": "indexed", + "rgb565_colors": 181, + "source_bytes": 17398, + "source_mode": "RGBA", + "source_sha256": "1e01c151c668a0bb654698a3c236b432ddfe74d4e51d0d520fd545f51a26b0b8", + "width": 320 + }, + "jin1.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 7077, + "optimized_sha256": "a1a08187b41c9a6ba6f1cebcf9ef3d658210facdd53bd9b5fe514e90810a1f6d", + "output_mode": "indexed", + "rgb565_colors": 151, + "source_bytes": 19311, + "source_mode": "RGBA", + "source_sha256": "a048e4e9a74ad59d8835a4a4288cee5598b0f4b96de801bdfd3a38dde4c70b72", + "width": 320 + }, + "listening0.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 6165, + "optimized_sha256": "b0c13b53a7beeb4982f635ba80064c5f312cb5b46f24d922ba5a87a9b12ddc77", + "output_mode": "indexed", + "rgb565_colors": 86, + "source_bytes": 19615, + "source_mode": "RGBA", + "source_sha256": "0cda107857039556674d50a45e11da93e22305e86770573fd80e4ba71d711ec6", + "width": 320 + }, + "listening1.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 4939, + "optimized_sha256": "fb33b584a873555ccdcfbc770ead6d58ffb571337e2bee24d771c0b4083cf63f", + "output_mode": "indexed", + "rgb565_colors": 82, + "source_bytes": 15072, + "source_mode": "RGBA", + "source_sha256": "67712a230ef3a3c989f224a18a30cc88593fe5eebfb1d7058f26f78ea1383c84", + "width": 320 + }, + "listening2.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 3577, + "optimized_sha256": "c409665d7d17ecf773f6dcf66093c7f6cddb4216038d309668e3da89eadfb64d", + "output_mode": "indexed", + "rgb565_colors": 76, + "source_bytes": 10758, + "source_mode": "RGBA", + "source_sha256": "b1ef51089a82077e3141526a9fc5ba06ae202ede6266e4ded12b410b682946e1", + "width": 320 + }, + "lowbat.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 36, + "optimized_bytes": 3527, + "optimized_sha256": "63b23e0746b2a0ffe2f2abba93303024b3337eccac7abe36e8abe2a08f751a94", + "output_mode": "indexed", + "rgb565_colors": 71, + "source_bytes": 10222, + "source_mode": "RGBA", + "source_sha256": "6249627d295dd1903ca5fc88bb08dfba111492f3ea04bda6b5d15e073975b59f", + "width": 232 + }, + "mu0.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 7270, + "optimized_sha256": "b19d6923a48626cb39c8db03be9ba2b7efbd9d1ab848920444f0039f8581acef", + "output_mode": "indexed", + "rgb565_colors": 163, + "source_bytes": 19742, + "source_mode": "RGBA", + "source_sha256": "4bea80b703e5d045047a93fd249c7067954091d3678fa070339b09cc9f78f311", + "width": 320 + }, + "mu1.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 7208, + "optimized_sha256": "1158ed4a5dd9e1d1c07a9a63e1a740c494e5ae7049d73d411b193f7539734a77", + "output_mode": "indexed", + "rgb565_colors": 127, + "source_bytes": 19483, + "source_mode": "RGBA", + "source_sha256": "a93dcf96f9edf8645d23e06a973174013960ea6937a0c99ef976610abba7e538", + "width": 320 + }, + "muyu0.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 6924, + "optimized_sha256": "60bdffd2a71830eb522be36ae9b934b2ccbae5b6b4bfc30e4d24c4fd5e0c3ee0", + "output_mode": "indexed", + "rgb565_colors": 143, + "source_bytes": 17340, + "source_mode": "RGBA", + "source_sha256": "111011f6c1a1cbb1b18598c3315dfa9ead76bb16bfccf96b86b3b4a64982e0ee", + "width": 320 + }, + "muyu1.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 8325, + "optimized_sha256": "9a9b245c4a2868e2184023041d4b20c427c12699ecd3d61e34accbd537fa1212", + "output_mode": "indexed", + "rgb565_colors": 172, + "source_bytes": 21462, + "source_mode": "RGBA", + "source_sha256": "f9defb109cb9f653ff1a4feeb368b6d98cdb33e7934d571f6c21d968ed90036c", + "width": 320 + }, + "muyu2.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 12523, + "optimized_sha256": "fdfa6c8beaac2043c01609f7c78bd19ace1cff597303ee541e50d266849ced6d", + "output_mode": "indexed", + "rgb565_colors": 188, + "source_bytes": 34079, + "source_mode": "RGBA", + "source_sha256": "213149b8de7a976239d44a597e33e66e9901eb2c2f18de78162bd7ff6d9e7e12", + "width": 320 + }, + "north0.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 5527, + "optimized_sha256": "b8921f25930de2f0abff9321e1fd7ddb385579ca71a3ee2e3adfbe0e171d54a6", + "output_mode": "indexed", + "rgb565_colors": 196, + "source_bytes": 19314, + "source_mode": "RGBA", + "source_sha256": "a43c3f65fcfd769f0a778c863672d9227baa14f86239577f755e0631c326baa1", + "width": 320 + }, + "northeast0.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 6276, + "optimized_sha256": "4db0fb32cfec5538741cd3031a52b8e31d36e6e03b21e9a232ccc1d258da63b1", + "output_mode": "indexed", + "rgb565_colors": 207, + "source_bytes": 21808, + "source_mode": "RGBA", + "source_sha256": "f8caea8bc9ba2a75d140ab71658bfcfafac16465ab149d7ccf1f6cbc5b10d67c", + "width": 320 + }, + "northwest0.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 5496, + "optimized_sha256": "b692eeb4e26795b057ef22879fa227c47458c2482d0325f9767a25851e820a7f", + "output_mode": "indexed", + "rgb565_colors": 206, + "source_bytes": 19143, + "source_mode": "RGBA", + "source_sha256": "562b2e840979852e4b228bc83595e83c4f08b5b53183ff705e3ea84c928ac565", + "width": 320 + }, + "ota1.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 8745, + "optimized_sha256": "a7234c7ef0c09aea89151452163cd8545ec70f12aa8397c9f3c423326856c534", + "output_mode": "indexed", + "rgb565_colors": 141, + "source_bytes": 25911, + "source_mode": "RGBA", + "source_sha256": "861367c662b63b1068234de73b33ba1abd10d0b4be6f0139606b8a536b71bfe9", + "width": 320 + }, + "ota2.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 7585, + "optimized_sha256": "12312f0ed5bda49e00d31ff60bd7c615e0cee863602f1eb9c39e26628cc3efd7", + "output_mode": "indexed", + "rgb565_colors": 148, + "source_bytes": 22397, + "source_mode": "RGBA", + "source_sha256": "f927fc9f98f134969f1291ac766837dae885ccaa1c57c2b71ab5dc3f18d6a065", + "width": 320 + }, + "ota3.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 7525, + "optimized_sha256": "ed317399776c56adf943c661b6febeeb3d8167af765ad0f382dce980ef5a3e22", + "output_mode": "indexed", + "rgb565_colors": 155, + "source_bytes": 22079, + "source_mode": "RGBA", + "source_sha256": "2b1b35e92a3122b356867de895605ca15a7b83ed1781edc1dc8a5b34c21d3747", + "width": 320 + }, + "qianzhiwuxing0.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 5811, + "optimized_sha256": "c821f5f16e9dbec6f9a5f25fcc7817be30a83020d0558a257d0702397757a7fa", + "output_mode": "indexed", + "rgb565_colors": 240, + "source_bytes": 16670, + "source_mode": "RGBA", + "source_sha256": "c886654384c9c4355c1a9830f9646dd237b119fbadc7256b81aa3214d43eb793", + "width": 320 + }, + "qrcode.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 11039, + "optimized_sha256": "fdd62069b6e7302a1a2012031f7570470ffaedd22d33f9b99dcf03c76c479322", + "output_mode": "indexed", + "rgb565_colors": 140, + "source_bytes": 18971, + "source_mode": "RGB", + "source_sha256": "202d5471929454f550daf09ace128f8ed852b92a8b4edc1d10bef039dafb343a", + "width": 320 + }, + "shui0.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 6640, + "optimized_sha256": "8e35a7f8dcc96d1b75e474c73444d06ee92c6649c644e4b4a54f7c8d950ee9e0", + "output_mode": "indexed", + "rgb565_colors": 183, + "source_bytes": 18086, + "source_mode": "RGBA", + "source_sha256": "ccff9672f0d28c5f6811a3bfbed29560779884f6b4df0630d7e754edec0f0ba3", + "width": 320 + }, + "shui1.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 6932, + "optimized_sha256": "dc53f98192faea726edaed7dcf4b1f8453927271c78af668673e27e1a4ac17ec", + "output_mode": "indexed", + "rgb565_colors": 120, + "source_bytes": 18478, + "source_mode": "RGBA", + "source_sha256": "0f397a83328f25993116e3e2a58ee9d770266f93caa89e19a04eaaf6260ea12e", + "width": 320 + }, + "south0.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 6609, + "optimized_sha256": "18245dcc7411dc007e717c8078a11e188888bd3378abd6da31bfe193ef93b7c3", + "output_mode": "indexed", + "rgb565_colors": 219, + "source_bytes": 22767, + "source_mode": "RGBA", + "source_sha256": "bf136b440a2f71aba0e95d7e2754271450ab37f041b6896390655b8eef46b5d3", + "width": 320 + }, + "southeast0.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 7354, + "optimized_sha256": "90c140195f957c1fdad869c8b5430c644a9e0292629f8430bd85bee3502356be", + "output_mode": "indexed", + "rgb565_colors": 225, + "source_bytes": 24902, + "source_mode": "RGBA", + "source_sha256": "b62ba4d83242d98817a15c7c5259585370e4e59f43b3a1b9f2dd8156223c9177", + "width": 320 + }, + "southwest0.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 6599, + "optimized_sha256": "635db54b131aae2e60d4e4339bac7a843334e02d40d5c0b3a5c43de7d53b1691", + "output_mode": "indexed", + "rgb565_colors": 221, + "source_bytes": 22298, + "source_mode": "RGBA", + "source_sha256": "611cda639555155e51dcd7e52b3bd8c4d6cd60621e1200e38afd3b56f15d73b0", + "width": 320 + }, + "spin0.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 6447, + "optimized_sha256": "8143f4eff4056188edbe4187e380811bf8dfd336f9e2fbcf9a61119ebaf60b28", + "output_mode": "indexed", + "rgb565_colors": 127, + "source_bytes": 17071, + "source_mode": "RGBA", + "source_sha256": "c1393c246dda14f956d66a4a39522f04af75d0a8489331f59501dd0e7a954326", + "width": 320 + }, + "tu0.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 7028, + "optimized_sha256": "2b11ac86144c13232a9b204f4a47978f27754fe22675463f2f32693f74061c51", + "output_mode": "indexed", + "rgb565_colors": 163, + "source_bytes": 18101, + "source_mode": "RGBA", + "source_sha256": "6a573462697e5de3c33ad5eaeced7c5ed6447b4b68173d8b0248bdf5098497d6", + "width": 320 + }, + "tu1.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 7381, + "optimized_sha256": "2702ad4a18285f25bc0c6414e40d532517db657115ad6f24dbe0f4e824d9b51b", + "output_mode": "indexed", + "rgb565_colors": 129, + "source_bytes": 20059, + "source_mode": "RGBA", + "source_sha256": "166342626a5f3c5c97103e267620d1d5336f00734fffe2e825598f4fcee2a8f4", + "width": 320 + }, + "west0.png": { + "alpha_range": [ + 255, + 255 + ], + "height": 240, + "optimized_bytes": 5164, + "optimized_sha256": "5cdf3887e214b17dae772e38458b92d7dd6840d7c8d834aaadc5d8a78419ad41", + "output_mode": "indexed", + "rgb565_colors": 212, + "source_bytes": 18011, + "source_mode": "RGBA", + "source_sha256": "614123cd50d8a7ff9b730154697f5c79b077f5d0fc7a41b5ebb44b973115bc93", + "width": 320 + } + }, + "generator": "tools/optimize_lcd_assets.py", + "pillow_version": "12.3.0", + "totals": { + "asset_count": 59, + "optimized_bytes": 381384, + "source_bytes": 1069465 + }, + "version": 1 +} diff --git a/components/platform/assets/lcd_optimized/mu0.png b/components/platform/assets/lcd_optimized/mu0.png new file mode 100644 index 0000000..5d5d515 Binary files /dev/null and b/components/platform/assets/lcd_optimized/mu0.png differ diff --git a/components/platform/assets/lcd_optimized/mu1.png b/components/platform/assets/lcd_optimized/mu1.png new file mode 100644 index 0000000..903e4ea Binary files /dev/null and b/components/platform/assets/lcd_optimized/mu1.png differ diff --git a/components/platform/assets/lcd_optimized/muyu0.png b/components/platform/assets/lcd_optimized/muyu0.png new file mode 100644 index 0000000..1c5e1ab Binary files /dev/null and b/components/platform/assets/lcd_optimized/muyu0.png differ diff --git a/components/platform/assets/lcd_optimized/muyu1.png b/components/platform/assets/lcd_optimized/muyu1.png new file mode 100644 index 0000000..00f7711 Binary files /dev/null and b/components/platform/assets/lcd_optimized/muyu1.png differ diff --git a/components/platform/assets/lcd_optimized/muyu2.png b/components/platform/assets/lcd_optimized/muyu2.png new file mode 100644 index 0000000..62a8c8b Binary files /dev/null and b/components/platform/assets/lcd_optimized/muyu2.png differ diff --git a/components/platform/assets/lcd_optimized/north0.png b/components/platform/assets/lcd_optimized/north0.png new file mode 100644 index 0000000..6ff59f6 Binary files /dev/null and b/components/platform/assets/lcd_optimized/north0.png differ diff --git a/components/platform/assets/lcd_optimized/northeast0.png b/components/platform/assets/lcd_optimized/northeast0.png new file mode 100644 index 0000000..4275643 Binary files /dev/null and b/components/platform/assets/lcd_optimized/northeast0.png differ diff --git a/components/platform/assets/lcd_optimized/northwest0.png b/components/platform/assets/lcd_optimized/northwest0.png new file mode 100644 index 0000000..86c4f48 Binary files /dev/null and b/components/platform/assets/lcd_optimized/northwest0.png differ diff --git a/components/platform/assets/lcd_optimized/ota1.png b/components/platform/assets/lcd_optimized/ota1.png new file mode 100644 index 0000000..a21befe Binary files /dev/null and b/components/platform/assets/lcd_optimized/ota1.png differ diff --git a/components/platform/assets/lcd_optimized/ota2.png b/components/platform/assets/lcd_optimized/ota2.png new file mode 100644 index 0000000..a8d2b41 Binary files /dev/null and b/components/platform/assets/lcd_optimized/ota2.png differ diff --git a/components/platform/assets/lcd_optimized/ota3.png b/components/platform/assets/lcd_optimized/ota3.png new file mode 100644 index 0000000..116fd95 Binary files /dev/null and b/components/platform/assets/lcd_optimized/ota3.png differ diff --git a/components/platform/assets/lcd_optimized/qianzhiwuxing0.png b/components/platform/assets/lcd_optimized/qianzhiwuxing0.png new file mode 100644 index 0000000..14add83 Binary files /dev/null and b/components/platform/assets/lcd_optimized/qianzhiwuxing0.png differ diff --git a/components/platform/assets/lcd_optimized/qrcode.png b/components/platform/assets/lcd_optimized/qrcode.png new file mode 100644 index 0000000..442476a Binary files /dev/null and b/components/platform/assets/lcd_optimized/qrcode.png differ diff --git a/components/platform/assets/lcd_optimized/shui0.png b/components/platform/assets/lcd_optimized/shui0.png new file mode 100644 index 0000000..45ca4dc Binary files /dev/null and b/components/platform/assets/lcd_optimized/shui0.png differ diff --git a/components/platform/assets/lcd_optimized/shui1.png b/components/platform/assets/lcd_optimized/shui1.png new file mode 100644 index 0000000..9ad67b7 Binary files /dev/null and b/components/platform/assets/lcd_optimized/shui1.png differ diff --git a/components/platform/assets/lcd_optimized/south0.png b/components/platform/assets/lcd_optimized/south0.png new file mode 100644 index 0000000..c19d2e4 Binary files /dev/null and b/components/platform/assets/lcd_optimized/south0.png differ diff --git a/components/platform/assets/lcd_optimized/southeast0.png b/components/platform/assets/lcd_optimized/southeast0.png new file mode 100644 index 0000000..8f396c1 Binary files /dev/null and b/components/platform/assets/lcd_optimized/southeast0.png differ diff --git a/components/platform/assets/lcd_optimized/southwest0.png b/components/platform/assets/lcd_optimized/southwest0.png new file mode 100644 index 0000000..7ec12e9 Binary files /dev/null and b/components/platform/assets/lcd_optimized/southwest0.png differ diff --git a/components/platform/assets/lcd_optimized/spin0.png b/components/platform/assets/lcd_optimized/spin0.png new file mode 100644 index 0000000..a59b6dd Binary files /dev/null and b/components/platform/assets/lcd_optimized/spin0.png differ diff --git a/components/platform/assets/lcd_optimized/tu0.png b/components/platform/assets/lcd_optimized/tu0.png new file mode 100644 index 0000000..14b4c2e Binary files /dev/null and b/components/platform/assets/lcd_optimized/tu0.png differ diff --git a/components/platform/assets/lcd_optimized/tu1.png b/components/platform/assets/lcd_optimized/tu1.png new file mode 100644 index 0000000..76a7534 Binary files /dev/null and b/components/platform/assets/lcd_optimized/tu1.png differ diff --git a/components/platform/assets/lcd_optimized/west0.png b/components/platform/assets/lcd_optimized/west0.png new file mode 100644 index 0000000..7ceaaaa Binary files /dev/null and b/components/platform/assets/lcd_optimized/west0.png differ diff --git a/components/platform/assets/lcd/OTA.gif b/components/platform/assets/lcd_source/OTA.gif similarity index 100% rename from components/platform/assets/lcd/OTA.gif rename to components/platform/assets/lcd_source/OTA.gif diff --git a/components/platform/assets/lcd/buyunqian1.png b/components/platform/assets/lcd_source/buyunqian1.png similarity index 100% rename from components/platform/assets/lcd/buyunqian1.png rename to components/platform/assets/lcd_source/buyunqian1.png diff --git a/components/platform/assets/lcd/charging.png b/components/platform/assets/lcd_source/charging.png similarity index 100% rename from components/platform/assets/lcd/charging.png rename to components/platform/assets/lcd_source/charging.png diff --git a/components/platform/assets/lcd/chouqian_buyun0.png b/components/platform/assets/lcd_source/chouqian_buyun0.png similarity index 100% rename from components/platform/assets/lcd/chouqian_buyun0.png rename to components/platform/assets/lcd_source/chouqian_buyun0.png diff --git a/components/platform/assets/lcd/chouqian_buyun1.png b/components/platform/assets/lcd_source/chouqian_buyun1.png similarity index 100% rename from components/platform/assets/lcd/chouqian_buyun1.png rename to components/platform/assets/lcd_source/chouqian_buyun1.png diff --git a/components/platform/assets/lcd/chouqian_buyun2.png b/components/platform/assets/lcd_source/chouqian_buyun2.png similarity index 100% rename from components/platform/assets/lcd/chouqian_buyun2.png rename to components/platform/assets/lcd_source/chouqian_buyun2.png diff --git a/components/platform/assets/lcd/chouqian_heping0.png b/components/platform/assets/lcd_source/chouqian_heping0.png similarity index 100% rename from components/platform/assets/lcd/chouqian_heping0.png rename to components/platform/assets/lcd_source/chouqian_heping0.png diff --git a/components/platform/assets/lcd/chouqian_heping1.png b/components/platform/assets/lcd_source/chouqian_heping1.png similarity index 100% rename from components/platform/assets/lcd/chouqian_heping1.png rename to components/platform/assets/lcd_source/chouqian_heping1.png diff --git a/components/platform/assets/lcd/chouqian_heping2.png b/components/platform/assets/lcd_source/chouqian_heping2.png similarity index 100% rename from components/platform/assets/lcd/chouqian_heping2.png rename to components/platform/assets/lcd_source/chouqian_heping2.png diff --git a/components/platform/assets/lcd/chouqian_qianzhi0.png b/components/platform/assets/lcd_source/chouqian_qianzhi0.png similarity index 100% rename from components/platform/assets/lcd/chouqian_qianzhi0.png rename to components/platform/assets/lcd_source/chouqian_qianzhi0.png diff --git a/components/platform/assets/lcd/chouqian_qianzhi1.png b/components/platform/assets/lcd_source/chouqian_qianzhi1.png similarity index 100% rename from components/platform/assets/lcd/chouqian_qianzhi1.png rename to components/platform/assets/lcd_source/chouqian_qianzhi1.png diff --git a/components/platform/assets/lcd/chouqian_qianzhi2.png b/components/platform/assets/lcd_source/chouqian_qianzhi2.png similarity index 100% rename from components/platform/assets/lcd/chouqian_qianzhi2.png rename to components/platform/assets/lcd_source/chouqian_qianzhi2.png diff --git a/components/platform/assets/lcd/chouqian_shangshang0.png b/components/platform/assets/lcd_source/chouqian_shangshang0.png similarity index 100% rename from components/platform/assets/lcd/chouqian_shangshang0.png rename to components/platform/assets/lcd_source/chouqian_shangshang0.png diff --git a/components/platform/assets/lcd/chouqian_shangshang1.png b/components/platform/assets/lcd_source/chouqian_shangshang1.png similarity index 100% rename from components/platform/assets/lcd/chouqian_shangshang1.png rename to components/platform/assets/lcd_source/chouqian_shangshang1.png diff --git a/components/platform/assets/lcd/chouqian_shangshang2.png b/components/platform/assets/lcd_source/chouqian_shangshang2.png similarity index 100% rename from components/platform/assets/lcd/chouqian_shangshang2.png rename to components/platform/assets/lcd_source/chouqian_shangshang2.png diff --git a/components/platform/assets/lcd/chouqian_xiaoji0.png b/components/platform/assets/lcd_source/chouqian_xiaoji0.png similarity index 100% rename from components/platform/assets/lcd/chouqian_xiaoji0.png rename to components/platform/assets/lcd_source/chouqian_xiaoji0.png diff --git a/components/platform/assets/lcd/chouqian_xiaoji1.png b/components/platform/assets/lcd_source/chouqian_xiaoji1.png similarity index 100% rename from components/platform/assets/lcd/chouqian_xiaoji1.png rename to components/platform/assets/lcd_source/chouqian_xiaoji1.png diff --git a/components/platform/assets/lcd/chouqian_xiaoji2.png b/components/platform/assets/lcd_source/chouqian_xiaoji2.png similarity index 100% rename from components/platform/assets/lcd/chouqian_xiaoji2.png rename to components/platform/assets/lcd_source/chouqian_xiaoji2.png diff --git a/components/platform/assets/lcd/chouqian_zhongshang0.png b/components/platform/assets/lcd_source/chouqian_zhongshang0.png similarity index 100% rename from components/platform/assets/lcd/chouqian_zhongshang0.png rename to components/platform/assets/lcd_source/chouqian_zhongshang0.png diff --git a/components/platform/assets/lcd/chouqian_zhongshang1.png b/components/platform/assets/lcd_source/chouqian_zhongshang1.png similarity index 100% rename from components/platform/assets/lcd/chouqian_zhongshang1.png rename to components/platform/assets/lcd_source/chouqian_zhongshang1.png diff --git a/components/platform/assets/lcd/chouqian_zhongshang2.png b/components/platform/assets/lcd_source/chouqian_zhongshang2.png similarity index 100% rename from components/platform/assets/lcd/chouqian_zhongshang2.png rename to components/platform/assets/lcd_source/chouqian_zhongshang2.png diff --git a/components/platform/assets/lcd/east0.png b/components/platform/assets/lcd_source/east0.png similarity index 100% rename from components/platform/assets/lcd/east0.png rename to components/platform/assets/lcd_source/east0.png diff --git a/components/platform/assets/lcd/huo0.png b/components/platform/assets/lcd_source/huo0.png similarity index 100% rename from components/platform/assets/lcd/huo0.png rename to components/platform/assets/lcd_source/huo0.png diff --git a/components/platform/assets/lcd/huo1.png b/components/platform/assets/lcd_source/huo1.png similarity index 100% rename from components/platform/assets/lcd/huo1.png rename to components/platform/assets/lcd_source/huo1.png diff --git a/components/platform/assets/lcd/icon_huo0.png b/components/platform/assets/lcd_source/icon_huo0.png similarity index 100% rename from components/platform/assets/lcd/icon_huo0.png rename to components/platform/assets/lcd_source/icon_huo0.png diff --git a/components/platform/assets/lcd/icon_jin0.png b/components/platform/assets/lcd_source/icon_jin0.png similarity index 100% rename from components/platform/assets/lcd/icon_jin0.png rename to components/platform/assets/lcd_source/icon_jin0.png diff --git a/components/platform/assets/lcd/icon_mu0.png b/components/platform/assets/lcd_source/icon_mu0.png similarity index 100% rename from components/platform/assets/lcd/icon_mu0.png rename to components/platform/assets/lcd_source/icon_mu0.png diff --git a/components/platform/assets/lcd/icon_shui0.png b/components/platform/assets/lcd_source/icon_shui0.png similarity index 100% rename from components/platform/assets/lcd/icon_shui0.png rename to components/platform/assets/lcd_source/icon_shui0.png diff --git a/components/platform/assets/lcd/icon_tu0.png b/components/platform/assets/lcd_source/icon_tu0.png similarity index 100% rename from components/platform/assets/lcd/icon_tu0.png rename to components/platform/assets/lcd_source/icon_tu0.png diff --git a/components/platform/assets/lcd/jichubiaoqing0.png b/components/platform/assets/lcd_source/jichubiaoqing0.png similarity index 100% rename from components/platform/assets/lcd/jichubiaoqing0.png rename to components/platform/assets/lcd_source/jichubiaoqing0.png diff --git a/components/platform/assets/lcd/jichubiaoqing1.png b/components/platform/assets/lcd_source/jichubiaoqing1.png similarity index 100% rename from components/platform/assets/lcd/jichubiaoqing1.png rename to components/platform/assets/lcd_source/jichubiaoqing1.png diff --git a/components/platform/assets/lcd/jichubiaoqing2.png b/components/platform/assets/lcd_source/jichubiaoqing2.png similarity index 100% rename from components/platform/assets/lcd/jichubiaoqing2.png rename to components/platform/assets/lcd_source/jichubiaoqing2.png diff --git a/components/platform/assets/lcd/jin0.png b/components/platform/assets/lcd_source/jin0.png similarity index 100% rename from components/platform/assets/lcd/jin0.png rename to components/platform/assets/lcd_source/jin0.png diff --git a/components/platform/assets/lcd/jin1.png b/components/platform/assets/lcd_source/jin1.png similarity index 100% rename from components/platform/assets/lcd/jin1.png rename to components/platform/assets/lcd_source/jin1.png diff --git a/components/platform/assets/lcd/listening0.png b/components/platform/assets/lcd_source/listening0.png similarity index 100% rename from components/platform/assets/lcd/listening0.png rename to components/platform/assets/lcd_source/listening0.png diff --git a/components/platform/assets/lcd/listening1.png b/components/platform/assets/lcd_source/listening1.png similarity index 100% rename from components/platform/assets/lcd/listening1.png rename to components/platform/assets/lcd_source/listening1.png diff --git a/components/platform/assets/lcd/listening2.png b/components/platform/assets/lcd_source/listening2.png similarity index 100% rename from components/platform/assets/lcd/listening2.png rename to components/platform/assets/lcd_source/listening2.png diff --git a/components/platform/assets/lcd/lowbat.png b/components/platform/assets/lcd_source/lowbat.png similarity index 100% rename from components/platform/assets/lcd/lowbat.png rename to components/platform/assets/lcd_source/lowbat.png diff --git a/components/platform/assets/lcd/mu0.png b/components/platform/assets/lcd_source/mu0.png similarity index 100% rename from components/platform/assets/lcd/mu0.png rename to components/platform/assets/lcd_source/mu0.png diff --git a/components/platform/assets/lcd/mu1.png b/components/platform/assets/lcd_source/mu1.png similarity index 100% rename from components/platform/assets/lcd/mu1.png rename to components/platform/assets/lcd_source/mu1.png diff --git a/components/platform/assets/lcd/muyu0.png b/components/platform/assets/lcd_source/muyu0.png similarity index 100% rename from components/platform/assets/lcd/muyu0.png rename to components/platform/assets/lcd_source/muyu0.png diff --git a/components/platform/assets/lcd/muyu1.png b/components/platform/assets/lcd_source/muyu1.png similarity index 100% rename from components/platform/assets/lcd/muyu1.png rename to components/platform/assets/lcd_source/muyu1.png diff --git a/components/platform/assets/lcd/muyu2.png b/components/platform/assets/lcd_source/muyu2.png similarity index 100% rename from components/platform/assets/lcd/muyu2.png rename to components/platform/assets/lcd_source/muyu2.png diff --git a/components/platform/assets/lcd/north0.png b/components/platform/assets/lcd_source/north0.png similarity index 100% rename from components/platform/assets/lcd/north0.png rename to components/platform/assets/lcd_source/north0.png diff --git a/components/platform/assets/lcd/northeast0.png b/components/platform/assets/lcd_source/northeast0.png similarity index 100% rename from components/platform/assets/lcd/northeast0.png rename to components/platform/assets/lcd_source/northeast0.png diff --git a/components/platform/assets/lcd/northwest0.png b/components/platform/assets/lcd_source/northwest0.png similarity index 100% rename from components/platform/assets/lcd/northwest0.png rename to components/platform/assets/lcd_source/northwest0.png diff --git a/components/platform/assets/lcd/ota1.png b/components/platform/assets/lcd_source/ota1.png similarity index 100% rename from components/platform/assets/lcd/ota1.png rename to components/platform/assets/lcd_source/ota1.png diff --git a/components/platform/assets/lcd/ota2.png b/components/platform/assets/lcd_source/ota2.png similarity index 100% rename from components/platform/assets/lcd/ota2.png rename to components/platform/assets/lcd_source/ota2.png diff --git a/components/platform/assets/lcd/ota3.png b/components/platform/assets/lcd_source/ota3.png similarity index 100% rename from components/platform/assets/lcd/ota3.png rename to components/platform/assets/lcd_source/ota3.png diff --git a/components/platform/assets/lcd/qianzhiwuxing0.png b/components/platform/assets/lcd_source/qianzhiwuxing0.png similarity index 100% rename from components/platform/assets/lcd/qianzhiwuxing0.png rename to components/platform/assets/lcd_source/qianzhiwuxing0.png diff --git a/components/platform/assets/lcd/qrcode.png b/components/platform/assets/lcd_source/qrcode.png similarity index 100% rename from components/platform/assets/lcd/qrcode.png rename to components/platform/assets/lcd_source/qrcode.png diff --git a/components/platform/assets/lcd/shui0.png b/components/platform/assets/lcd_source/shui0.png similarity index 100% rename from components/platform/assets/lcd/shui0.png rename to components/platform/assets/lcd_source/shui0.png diff --git a/components/platform/assets/lcd/shui1.png b/components/platform/assets/lcd_source/shui1.png similarity index 100% rename from components/platform/assets/lcd/shui1.png rename to components/platform/assets/lcd_source/shui1.png diff --git a/components/platform/assets/lcd/south0.png b/components/platform/assets/lcd_source/south0.png similarity index 100% rename from components/platform/assets/lcd/south0.png rename to components/platform/assets/lcd_source/south0.png diff --git a/components/platform/assets/lcd/southeast0.png b/components/platform/assets/lcd_source/southeast0.png similarity index 100% rename from components/platform/assets/lcd/southeast0.png rename to components/platform/assets/lcd_source/southeast0.png diff --git a/components/platform/assets/lcd/southwest0.png b/components/platform/assets/lcd_source/southwest0.png similarity index 100% rename from components/platform/assets/lcd/southwest0.png rename to components/platform/assets/lcd_source/southwest0.png diff --git a/components/platform/assets/lcd/spin0.png b/components/platform/assets/lcd_source/spin0.png similarity index 100% rename from components/platform/assets/lcd/spin0.png rename to components/platform/assets/lcd_source/spin0.png diff --git a/components/platform/assets/lcd/tu0.png b/components/platform/assets/lcd_source/tu0.png similarity index 100% rename from components/platform/assets/lcd/tu0.png rename to components/platform/assets/lcd_source/tu0.png diff --git a/components/platform/assets/lcd/tu1.png b/components/platform/assets/lcd_source/tu1.png similarity index 100% rename from components/platform/assets/lcd/tu1.png rename to components/platform/assets/lcd_source/tu1.png diff --git a/components/platform/assets/lcd/west0.png b/components/platform/assets/lcd_source/west0.png similarity index 100% rename from components/platform/assets/lcd/west0.png rename to components/platform/assets/lcd_source/west0.png diff --git a/docs/lcd-image-asset-optimization.zh-CN.md b/docs/lcd-image-asset-optimization.zh-CN.md new file mode 100644 index 0000000..7d2c9d4 --- /dev/null +++ b/docs/lcd-image-asset-optimization.zh-CN.md @@ -0,0 +1,101 @@ +# LCD 图片资源优化与维护 + +## 目标 + +固件使用的 LCD 为 RGB565 显示格式,而设计工具导出的 PNG 通常是 RGB888 或 RGBA8888。原始 PNG 中包含 LCD 无法显示的颜色精度,部分图片还携带未实际使用的 Alpha 通道。 + +本项目在不改变 LCD 最终 RGB565 像素的前提下,将固件图片转换成 RGB565 颜色精度,并在颜色数量不超过 256 时保存为索引色 PNG,以降低 `gifpack` LittleFS 分区的占用。 + +正常固件编译直接使用已经提交到 Git 的优化图片,不要求开发环境安装 Pillow。只有修改或验证图片资源时才需要 Pillow。 + +## 目录结构 + +```text +components/platform/assets/ +├── lcd_source/ 原始设计图片,不直接打包进固件 +└── lcd_optimized/ 优化图片和 manifest.json,固件实际使用 +``` + +两个目录中的 PNG 文件名必须一致。运行时路径仍为 `/lcd/<文件名>.png`,业务代码不需要区分原图和优化图。 + +`lcd_source` 中的 `OTA.gif` 是历史资源,不属于当前 PNG 优化和 `gifpack` 打包清单。 + +## 优化规则 + +脚本 `tools/optimize_lcd_assets.py` 对每张 PNG 执行以下操作: + +1. 检查图片尺寸和 Alpha 范围。 +2. Alpha 全部为 255 时移除无用的 Alpha 通道。 +3. 将 RGB888 颜色对齐到 LCD 的 RGB565 精度。 +4. RGB565 颜色不超过 256 种时保存为索引色 PNG。 +5. 超过 256 种颜色时保存为普通 RGB PNG,不进行额外颜色合并。 +6. 逐像素比较原图和优化图转换后的 RGB565 数值。 +7. 生成包含文件哈希、尺寸、颜色数和容量的 `manifest.json`。 + +只要有一个 RGB565 像素不同,生成操作就会失败。发现透明或半透明图片时也会停止,避免静默删除有效透明度。 + +## 首次准备环境 + +建议使用独立 Python 虚拟环境,避免修改系统 Python: + +```bash +python3 -m venv .venv +. .venv/bin/activate +python -m pip install -r tools/requirements-assets.txt +python tools/optimize_lcd_assets.py check-env +``` + +ESP-IDF 自带的 Python 环境如果已经安装对应版本的 Pillow,也可以直接使用。 + +## 修改图片后的操作 + +只修改 `lcd_source` 中的原图,不要手动编辑 `lcd_optimized`。 + +生成全部优化图片: + +```bash +python tools/optimize_lcd_assets.py generate +``` + +执行完整的 RGB565 等价验证: + +```bash +python tools/optimize_lcd_assets.py verify +``` + +然后检查并提交以下内容: + +```text +components/platform/assets/lcd_source/ +components/platform/assets/lcd_optimized/ +components/platform/assets/lcd_optimized/manifest.json +``` + +不要反复处理 `lcd_optimized` 中的图片。每次必须从 `lcd_source` 原图重新生成,避免累计颜色或压缩处理误差。 + +## 普通固件编译 + +普通构建不生成图片,也不导入 Pillow。构建系统只执行基于 Python 标准库的哈希检查,确认: + +- 原图没有在生成后发生变化; +- 优化图片没有缺失或被手动修改; +- 原图、优化图和清单中的文件集合一致。 + +资源过期时,构建会停止并提示重新运行 `generate`。资源有效时,构建系统把 `lcd_optimized` 中的 PNG 复制到构建目录并生成 `gifpack` 分区镜像。 + +## 命令说明 + +| 命令 | 是否需要 Pillow | 用途 | +| --- | --- | --- | +| `check-env` | 是 | 检查 Python 和 Pillow 环境 | +| `generate` | 是 | 从原图重新生成全部优化图片和清单 | +| `check-current` | 否 | 通过哈希检查已提交资源是否同步 | +| `verify` | 是 | 检查哈希并逐像素验证 RGB565 等价性 | + +## 注意事项 + +- 优化图片针对 RGB565 LCD 制作,在高色深电脑屏幕上查看时可能与原始图片有细微颜色差异。 +- 对当前 LCD 而言,优化前后的输出像素完全一致。 +- 该方案主要降低 Flash 占用,不改变运行时 PNG 解码流程、帧缓存大小或动画播放接口。 +- 如果未来需要透明叠加效果,应先扩展转换和运行时合成方案,不能直接删除 Alpha。 +- 不要使用普通的“自动量化到 256 色”工具替代本脚本,否则可能合并 LCD 原本能够区分的颜色。 diff --git a/tools/check_architecture_guards.sh b/tools/check_architecture_guards.sh index 7ba9c5f..ded4b63 100644 --- a/tools/check_architecture_guards.sh +++ b/tools/check_architecture_guards.sh @@ -73,9 +73,9 @@ if ! rg -n 'DOMAIN_EXPRESSION_LISTENING|DOMAIN_EXPRESSION_TRIGGER_LOCAL_WAKE' \ components/control_plane/src/runtime/control_plane_runtime_workers.c >/dev/null 2>&1 || ! rg -n '^ listening[012]$' components/platform/CMakeLists.txt >/dev/null 2>&1 || ! rg -n 'listening[012]\.png' components/platform/src/lcd_direct_assets.c >/dev/null 2>&1 || - [[ ! -f components/platform/assets/lcd/listening0.png ]] || - [[ ! -f components/platform/assets/lcd/listening1.png ]] || - [[ ! -f components/platform/assets/lcd/listening2.png ]]; then + [[ ! -f components/platform/assets/lcd_optimized/listening0.png ]] || + [[ ! -f components/platform/assets/lcd_optimized/listening1.png ]] || + [[ ! -f components/platform/assets/lcd_optimized/listening2.png ]]; then fail "Local wake must show its packaged listening animation while gating speech uplink until the ready prompt finishes" fi if ! rg -n '#include "esp_afe_aec.h"' components/platform/src/multimodal/audio_pipeline.c >/dev/null 2>&1 || diff --git a/tools/optimize_lcd_assets.py b/tools/optimize_lcd_assets.py new file mode 100644 index 0000000..29fcee5 --- /dev/null +++ b/tools/optimize_lcd_assets.py @@ -0,0 +1,287 @@ +#!/usr/bin/env python3 +"""Generate and verify display-lossless PNG assets for the RGB565 LCD.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import shutil +import sys +import tempfile +from pathlib import Path +from typing import Any, Iterable + + +PROJECT_ROOT = Path(__file__).resolve().parent.parent +SOURCE_DIR = PROJECT_ROOT / "components" / "platform" / "assets" / "lcd_source" +OPTIMIZED_DIR = PROJECT_ROOT / "components" / "platform" / "assets" / "lcd_optimized" +MANIFEST_NAME = "manifest.json" +MANIFEST_VERSION = 1 + + +class AssetError(RuntimeError): + pass + + +def sha256_file(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as source: + for chunk in iter(lambda: source.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def png_files(directory: Path) -> list[Path]: + if not directory.is_dir(): + raise AssetError(f"Asset directory does not exist: {directory}") + return sorted(path for path in directory.iterdir() if path.is_file() and path.suffix.lower() == ".png") + + +def load_pillow() -> tuple[Any, str]: + try: + import PIL + from PIL import Image + except ImportError as exc: + raise AssetError( + "Pillow is required for this command. Install it with:\n" + f" {sys.executable} -m pip install -r {PROJECT_ROOT / 'tools' / 'requirements-assets.txt'}" + ) from exc + return Image, PIL.__version__ + + +def rgb565_value(pixel: tuple[int, int, int]) -> int: + red, green, blue = pixel + return ((red & 0xF8) << 8) | ((green & 0xFC) << 3) | (blue >> 3) + + +def expand_rgb565(pixel: tuple[int, int, int]) -> tuple[int, int, int]: + red, green, blue = pixel + red5 = red >> 3 + green6 = green >> 2 + blue5 = blue >> 3 + return ( + (red5 << 3) | (red5 >> 2), + (green6 << 2) | (green6 >> 4), + (blue5 << 3) | (blue5 >> 2), + ) + + +def save_indexed_png(image_module: Any, size: tuple[int, int], pixels: list[tuple[int, int, int]], output: Path) -> int: + palette_colors = sorted(set(pixels)) + if len(palette_colors) > 256: + raise AssetError("Indexed PNG requested with more than 256 colors") + + palette_index = {color: index for index, color in enumerate(palette_colors)} + indices = bytes(palette_index[pixel] for pixel in pixels) + indexed = image_module.frombytes("P", size, indices) + flat_palette = [channel for color in palette_colors for channel in color] + flat_palette.extend([0] * (768 - len(flat_palette))) + indexed.putpalette(flat_palette) + indexed.save(output, format="PNG", optimize=True, compress_level=9) + return len(palette_colors) + + +def assert_display_equivalent(source_rgb: Any, optimized_path: Path, image_module: Any) -> None: + with image_module.open(optimized_path) as optimized_image: + optimized_rgb = optimized_image.convert("RGB") + if optimized_rgb.size != source_rgb.size: + raise AssetError( + f"Image dimensions changed for {optimized_path.name}: " + f"{source_rgb.size} -> {optimized_rgb.size}" + ) + + for index, (source_pixel, optimized_pixel) in enumerate( + zip(source_rgb.get_flattened_data(), optimized_rgb.get_flattened_data()) + ): + if rgb565_value(source_pixel) != rgb565_value(optimized_pixel): + x = index % source_rgb.width + y = index // source_rgb.width + raise AssetError( + f"RGB565 mismatch in {optimized_path.name} at ({x}, {y}): " + f"{source_pixel} -> {optimized_pixel}" + ) + + +def optimize_one(source_path: Path, output_path: Path, image_module: Any) -> dict[str, Any]: + with image_module.open(source_path) as source_image: + source_mode = source_image.mode + rgba = source_image.convert("RGBA") + alpha_min, alpha_max = rgba.getchannel("A").getextrema() + if (alpha_min, alpha_max) != (255, 255): + raise AssetError( + f"Transparency is not supported by the RGB565 optimizer: {source_path.name} " + f"has alpha range ({alpha_min}, {alpha_max})" + ) + + source_rgb = rgba.convert("RGB") + source_pixels = list(source_rgb.get_flattened_data()) + rgb565_pixels = [expand_rgb565(pixel) for pixel in source_pixels] + color_count = len(set(rgb565_pixels)) + + if color_count <= 256: + output_mode = "indexed" + save_indexed_png(image_module, source_rgb.size, rgb565_pixels, output_path) + else: + output_mode = "rgb" + optimized_rgb = image_module.new("RGB", source_rgb.size) + optimized_rgb.putdata(rgb565_pixels) + optimized_rgb.save(output_path, format="PNG", optimize=True, compress_level=9) + + assert_display_equivalent(source_rgb, output_path, image_module) + return { + "source_sha256": sha256_file(source_path), + "optimized_sha256": sha256_file(output_path), + "width": source_rgb.width, + "height": source_rgb.height, + "source_mode": source_mode, + "alpha_range": [alpha_min, alpha_max], + "rgb565_colors": color_count, + "output_mode": output_mode, + "source_bytes": source_path.stat().st_size, + "optimized_bytes": output_path.stat().st_size, + } + + +def generate() -> None: + image_module, pillow_version = load_pillow() + sources = png_files(SOURCE_DIR) + if not sources: + raise AssetError(f"No PNG assets found in {SOURCE_DIR}") + + OPTIMIZED_DIR.parent.mkdir(parents=True, exist_ok=True) + temporary_dir = Path(tempfile.mkdtemp(prefix="lcd_optimized.", dir=OPTIMIZED_DIR.parent)) + try: + assets: dict[str, Any] = {} + for source_path in sources: + output_path = temporary_dir / source_path.name + assets[source_path.name] = optimize_one(source_path, output_path, image_module) + + manifest = { + "version": MANIFEST_VERSION, + "generator": "tools/optimize_lcd_assets.py", + "pillow_version": pillow_version, + "assets": assets, + "totals": { + "asset_count": len(assets), + "source_bytes": sum(item["source_bytes"] for item in assets.values()), + "optimized_bytes": sum(item["optimized_bytes"] for item in assets.values()), + }, + } + manifest_path = temporary_dir / MANIFEST_NAME + manifest_path.write_text( + json.dumps(manifest, ensure_ascii=True, indent=2, sort_keys=True) + "\n", + encoding="utf-8", + ) + + if OPTIMIZED_DIR.exists(): + shutil.rmtree(OPTIMIZED_DIR) + temporary_dir.replace(OPTIMIZED_DIR) + except Exception: + shutil.rmtree(temporary_dir, ignore_errors=True) + raise + + reduction = 100.0 * (1.0 - manifest["totals"]["optimized_bytes"] / manifest["totals"]["source_bytes"]) + print( + f"Generated {len(assets)} LCD assets: " + f"{manifest['totals']['source_bytes']} -> {manifest['totals']['optimized_bytes']} bytes " + f"({reduction:.1f}% reduction)" + ) + + +def load_manifest() -> dict[str, Any]: + manifest_path = OPTIMIZED_DIR / MANIFEST_NAME + if not manifest_path.is_file(): + raise AssetError(f"Asset manifest does not exist: {manifest_path}") + try: + manifest = json.loads(manifest_path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as exc: + raise AssetError(f"Cannot read asset manifest: {manifest_path}: {exc}") from exc + if manifest.get("version") != MANIFEST_VERSION or not isinstance(manifest.get("assets"), dict): + raise AssetError(f"Unsupported or invalid asset manifest: {manifest_path}") + return manifest + + +def check_current() -> dict[str, Any]: + manifest = load_manifest() + assets = manifest["assets"] + source_names = {path.name for path in png_files(SOURCE_DIR)} + optimized_names = {path.name for path in png_files(OPTIMIZED_DIR)} + manifest_names = set(assets) + + errors: list[str] = [] + if source_names != manifest_names: + errors.append( + f"Source/manifest file set differs: missing={sorted(source_names - manifest_names)}, " + f"stale={sorted(manifest_names - source_names)}" + ) + if optimized_names != manifest_names: + errors.append( + f"Optimized/manifest file set differs: missing={sorted(manifest_names - optimized_names)}, " + f"extra={sorted(optimized_names - manifest_names)}" + ) + + for name in sorted(source_names & manifest_names): + source_path = SOURCE_DIR / name + if sha256_file(source_path) != assets[name].get("source_sha256"): + errors.append(f"Source asset changed after optimization: {name}") + for name in sorted(optimized_names & manifest_names): + optimized_path = OPTIMIZED_DIR / name + if sha256_file(optimized_path) != assets[name].get("optimized_sha256"): + errors.append(f"Optimized asset changed after generation: {name}") + + if errors: + detail = "\n - ".join(errors) + raise AssetError( + "LCD optimized assets are out of date:\n" + f" - {detail}\n" + "Regenerate them with:\n" + f" {sys.executable} {Path(__file__).resolve()} generate" + ) + + print(f"LCD assets are current: {len(manifest_names)} files") + return manifest + + +def verify() -> None: + manifest = check_current() + image_module, _ = load_pillow() + for name in sorted(manifest["assets"]): + with image_module.open(SOURCE_DIR / name) as source_image: + source_rgb = source_image.convert("RGB") + assert_display_equivalent(source_rgb, OPTIMIZED_DIR / name, image_module) + print(f"RGB565 display equivalence verified: {len(manifest['assets'])} files") + + +def check_env() -> None: + _, pillow_version = load_pillow() + print(f"Asset environment is ready: Python {sys.version.split()[0]}, Pillow {pillow_version}") + + +def parse_args(arguments: Iterable[str]) -> argparse.Namespace: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "command", + choices=("check-env", "generate", "check-current", "verify"), + help="operation to perform", + ) + return parser.parse_args(arguments) + + +def main(arguments: Iterable[str] | None = None) -> int: + args = parse_args(sys.argv[1:] if arguments is None else arguments) + try: + { + "check-env": check_env, + "generate": generate, + "check-current": check_current, + "verify": verify, + }[args.command]() + except AssetError as exc: + print(f"error: {exc}", file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/requirements-assets.txt b/tools/requirements-assets.txt new file mode 100644 index 0000000..e7cc6c1 --- /dev/null +++ b/tools/requirements-assets.txt @@ -0,0 +1 @@ +Pillow==12.3.0