![image](https://hackmd.io/_uploads/BkTHe01wex.png) 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) ```