
micropython-ili9341搭配 [micropython-ili9341](https://github.com/rdagger/micropython-ili9341)控制並顯示圖片
```python=
# -*- coding: utf-8 -*-
"""
一個使用 Streamlit 製作的網站,用於將圖片轉換為 RAW RGB565 格式。
使用者可以上傳圖片,調整其尺寸,然後處理轉換並提供轉換後的 .raw 檔案供下載。
"""
import streamlit as st
from PIL import Image
from struct import pack, unpack # 引入 unpack 用於 RGB565 到圖片的轉換
import io
import os
import math # 引入 math 模組用於數學運算
def convert_image_to_rgb565(img_bytes, target_width, target_height):
"""
將圖片的位元組數據轉換為 RGB565 格式的位元組。
在轉換前會先將圖片縮放至指定尺寸。
Args:
img_bytes (BytesIO): 從 Streamlit 上傳的圖片位元組數據。
target_width (int): 縮放後的目標寬度。
target_height (int): 縮放後的目標高度。
Returns:
bytes: RGB565 格式的二進位數據。
"""
try:
# 開啟圖片並確保其為 RGB 格式
img = Image.open(img_bytes).convert('RGB')
# 根據使用者輸入的尺寸,重新調整圖片大小
# Image.Resampling.LANCZOS 是一種高品質的濾波演算法,適合縮圖
img_resized = img.resize((target_width, target_height), Image.Resampling.LANCZOS)
pixels = list(img_resized.getdata())
# 使用一個在記憶體中的二進位流來儲存結果
output_bin = io.BytesIO()
# 遍歷每個像素並進行轉換
for pix in pixels:
# 將 8-bit R, G, B 轉換為 5-bit R, 6-bit G, 5-bit B
r = (pix[0] >> 3) & 0x1F
g = (pix[1] >> 2) & 0x3F
b = (pix[2] >> 3) & 0x1F
# 將轉換後的 R, G, B 值組合成一個 16-bit 的數值
# 格式: RRRRRGGGGGGBBBBB
rgb565_pixel = (r << 11) | (g << 5) | b
# 將 16-bit 數值打包成 2 個位元組 (使用大端序)
output_bin.write(pack('>H', rgb565_pixel))
# 回到流的開頭,以便讀取其內容
output_bin.seek(0)
return output_bin.getvalue()
except Exception as e:
st.error(f"圖片處理時發生錯誤:{e}")
return None
def convert_rgb565_to_image(rgb565_data, width, height):
"""
將 RGB565 格式的位元組數據轉換回 PIL Image 物件。
此函數用於預覽轉換後的圖片,將 16-bit RGB565 像素解碼回 8-bit RGB。
Args:
rgb565_data (bytes): RGB565 格式的二進位數據。
width (int): 圖片的寬度。
height (int): 圖片的高度。
Returns:
PIL.Image.Image: 轉換後的 PIL Image 物件。
"""
img = Image.new('RGB', (width, height))
pixels = []
# 每個 RGB565 像素佔 2 個位元組
for i in range(0, len(rgb565_data), 2):
# 解包 16-bit 數值 (使用大端序)
# '>H' 表示大端序 (Big-endian) 的無符號短整數 (unsigned short)
rgb565_pixel = unpack('>H', rgb565_data[i:i+2])[0]
# 將 16-bit RGB565 轉換回 8-bit R, G, B
# R (5 bits): 提取最高 5 位,然後左移 3 位並複製最高 2 位到最低 2 位進行近似擴展
r = (rgb565_pixel >> 11) & 0x1F
r = (r << 3) | (r >> 2)
# G (6 bits): 提取中間 6 位,然後左移 2 位並複製最高 2 位到最低 2 位進行近似擴展
g = (rgb565_pixel >> 5) & 0x3F
g = (g << 2) | (g >> 4)
# B (5 bits): 提取最低 5 位,然後左移 3 位並複製最高 2 位到最低 2 位進行近似擴展
b = (rgb565_pixel >> 0) & 0x1F
b = (b << 3) | (b >> 2)
pixels.append((r, g, b))
# 將處理後的像素數據放入 PIL Image 物件
img.putdata(pixels)
return img
# --- Streamlit 網站介面 ---
# 設定頁面標題和圖示
st.set_page_config(page_title="RGB565 轉換器", page_icon="🖼️")
# 網站主標題
st.title("🖼️ 圖片轉 RGB565 格式工具")
# 網站說明
st.markdown("""
歡迎使用這個小工具!
您可以上傳一張圖片,**調整成您需要的尺寸**,然後系統會將其轉換為 **RAW RGB565** 格式的二進位檔案,並提供給您下載。
轉換完成後,您也可以直接在頁面上預覽轉換後的圖片。
""")
# 檔案上傳元件
uploaded_file = st.file_uploader(
"請選擇一張圖片...",
type=["png", "jpg", "jpeg", "bmp"],
help="支援常見的圖片格式"
)
# 如果使用者有上傳檔案
if uploaded_file is not None:
# 為了獲取圖片尺寸,需要先用 PIL 開啟
img_for_size = Image.open(uploaded_file)
original_width, original_height = img_for_size.size
# 顯示分隔線
st.divider()
# 建立兩欄佈局
col1, col2 = st.columns(2)
with col1:
st.subheader("原始圖片預覽")
# 顯示使用者上傳的原始圖片
# 已將 use_column_width=True 更新為 use_container_width=True
st.image(uploaded_file, caption=f"原始尺寸: {original_width} x {original_height}", use_container_width=True)
with col2:
st.subheader("調整尺寸與轉換")
st.write("請在下方選擇調整方式。")
# 選擇調整方式:指定尺寸或指定檔案大小
resize_method = st.radio(
"選擇調整方式:",
("指定尺寸", "指定檔案大小"),
index=0, # 預設為指定尺寸
help="您可以直接設定圖片的寬高,或設定目標檔案大小讓系統自動計算尺寸。"
)
new_width = original_width
new_height = original_height
info_message = ""
if resize_method == "指定尺寸":
# 讓使用者輸入新的寬度和高度,預設值為原始尺寸
new_width = st.number_input(
"新的寬度 (Width)",
min_value=1,
value=original_width,
step=1,
help="設定轉換後圖片的寬度(單位:像素)"
)
new_height = st.number_input(
"新的高度 (Height)",
min_value=1,
value=original_height,
step=1,
help="設定轉換後圖片的高度(單位:像素)"
)
info_message = f"目標尺寸: {new_width} x {new_height}"
elif resize_method == "指定檔案大小":
target_file_size_kb = st.number_input(
"目標檔案大小 (KB)",
min_value=1,
value=int((original_width * original_height * 2) / 1024), # 預設為原始 RGB565 大小
step=1,
help="設定轉換後的 .raw 檔案的目標大小(單位:KB)。系統將根據此大小自動調整圖片尺寸。"
)
# 計算目標像素數量 (每個 RGB565 像素佔 2 bytes)
target_pixels = (target_file_size_kb * 1024) / 2
# 原始圖片的像素數量
original_pixels = original_width * original_height
if target_pixels >= original_pixels:
# 如果目標大小大於或等於原始圖片的 RGB565 大小,則使用原始尺寸
new_width = original_width
new_height = original_height
info_message = f"目標檔案大小為 {target_file_size_kb} KB,原始圖片尺寸已符合要求,將使用原始尺寸: {new_width} x {new_height}"
else:
# 根據目標像素數量計算新的尺寸,保持長寬比
aspect_ratio = original_width / original_height
# 計算新的高度和寬度
calculated_height = math.sqrt(target_pixels / aspect_ratio)
calculated_width = calculated_height * aspect_ratio
# 轉換為整數,並確保至少為 1 像素
new_width = max(1, int(calculated_width))
new_height = max(1, int(calculated_height))
# 重新檢查計算後的實際像素數,以防浮點數誤差導致超出目標大小
# 如果計算出的尺寸導致實際像素數過大,則進一步微調
while (new_width * new_height * 2) / 1024 > target_file_size_kb and (new_width > 1 or new_height > 1):
if new_width / aspect_ratio > new_height: # 哪個維度更大,就先縮小哪個
new_width = max(1, new_width - 1)
else:
new_height = max(1, new_height - 1)
# 重新計算另一個維度以保持比例
if new_width / aspect_ratio < new_height:
new_height = max(1, int(new_width / aspect_ratio))
else:
new_width = max(1, int(new_height * aspect_ratio))
info_message = f"目標檔案大小為 {target_file_size_kb} KB,自動調整尺寸為: {new_width} x {new_height}"
# 轉換按鈕
if st.button("🚀 開始轉換", use_container_width=True, type="primary"):
st.info(info_message)
# 進行圖片轉換
with st.spinner('正在轉換中,請稍候...'):
raw_data = convert_image_to_rgb565(uploaded_file, new_width, new_height)
if raw_data:
# 產生下載檔名
base_filename = os.path.splitext(uploaded_file.name)[0]
download_filename = f"{base_filename}_{new_width}x{new_height}_rgb565.raw"
# 提供下載按鈕
st.download_button(
label="✅ 下載 .raw 檔案",
data=raw_data,
file_name=download_filename,
mime="application/octet-stream",
use_container_width=True
)
# 顯示檔案大小資訊
raw_data_size_kb = len(raw_data) / 1024
st.success(f"轉換成功!\n\n實際檔案大小: {raw_data_size_kb:.2f} KB")
# 顯示轉換後的圖片預覽
st.subheader("轉換後圖片預覽 (RGB565)")
# 將 RGB565 數據轉換回圖片以便預覽
converted_img = convert_rgb565_to_image(raw_data, new_width, new_height)
# 已將 use_column_width=True 更新為 use_container_width=True
st.image(converted_img, caption=f"轉換後尺寸: {new_width} x {new_height}", use_container_width=True)
# 頁腳
st.divider()
st.markdown("<div style='text-align: center;'>由 Gemini 協助開發</div>", unsafe_allow_html=True)
```
```python=
# main.py (顯示圖片專用 - 最終且最簡潔的版本)
import time
from machine import Pin, SPI
from ili9341 import Display, color565 # 確保 ili9341.py 和 main.py 在同一個目錄下
# --- 圖片檔案路徑 ---
IMAGE_FILE_PATH = 'rgb565.raw' # 假設圖片直接放在根目錄
# --- 硬體初始化 ---
print("--- Initializing Hardware ---")
PIN_SPI_SCK = 18
PIN_SPI_MOSI = 19
PIN_SPI_MISO = 16 # 通常 MISO 對於 LCD 寫入是不需要的,但保留
PIN_TFT_CS = 13
PIN_TFT_RST = 14
PIN_TFT_DC = 15
PIN_TFT_LED = 12
spi = SPI(0, baudrate=40000000, sck=Pin(PIN_SPI_SCK), mosi=Pin(PIN_SPI_MOSI), miso=Pin(PIN_SPI_MISO))
tft_cs = Pin(PIN_TFT_CS, Pin.OUT, value=1)
tft_rst = Pin(PIN_TFT_RST, Pin.OUT, value=1)
tft_dc = Pin(PIN_TFT_DC, Pin.OUT, value=1)
tft_led = Pin(PIN_TFT_LED, Pin.OUT, value=1) # 通常用於控制背光,保持開啟
# --- 顯示器實例化 ---
print("--- Initializing Display ---")
# 確保 rotation 設定符合你的物理顯示方向
# 如果你的螢幕是 320x240,rotation=270 應會將 240寬 x 320高 轉為 320寬 x 240高
display = Display(spi, dc=tft_dc, cs=tft_cs, rst=tft_rst, width=320, height=240, rotation=270)
display.clear(color565(0, 0, 0)) # 清除螢幕為黑色
# --- 主程式流程 ---
print("--- Starting Image Display ---")
try:
# 直接呼叫 Display 物件的 draw_image 方法,並傳入檔案路徑
# 庫內部會處理檔案開啟、讀取、分塊和顯示
# 預設參數 x=0, y=0, w=320, h=240 剛好符合你的 320x240 圖片需求
display.draw_image(IMAGE_FILE_PATH)
print(f"Successfully displayed {IMAGE_FILE_PATH}.")
except OSError as e:
print(f"Error: Could not load or display image '{IMAGE_FILE_PATH}'.")
print(f"Reason: {e}")
print("請確保圖片檔案存在且為正確的 320x240 RGB565 原始格式。")
display.clear(color565(255, 0, 0)) # 螢幕顯示紅色背景作為錯誤指示
except Exception as e:
print(f"發生意外錯誤: {e}")
display.clear(color565(255, 0, 0))
# --- 主迴圈 ---
while True:
# 圖片會持續顯示,這裡只是讓程式保持運行
time.sleep(10)
```