Bỏ qua, đến nội dung chính
Nastrotek
Bài viếtEmbedded UI

GIF, PNG, JPG hay ảnh C Array: Định dạng nào phù hợp với màn hình ESP32?

So sánh GIF, PNG, JPG và ảnh C array cho màn hình ESP32 theo flash, RAM, tốc độ giải mã, alpha, animation và chất lượng.

Chia sẻ

LinkedInFacebookX
A small round display showing an expressive face, surrounded by GIF, PNG, JPG, and C array format badges

Mình sẽ cùng bạn so sánh từng định dạng theo cách ESP32 thực sự lưu, giải mã và đưa ảnh lên màn hình.

Câu hỏi nào về asset trong LVGL rồi cũng quay về đúng một câu: ảnh này nên là GIF, PNG, JPG, hay chỉ là một C array biên dịch thẳng vào firmware? Câu trả lời thật là không có định dạng nào thắng tuyệt đối — mỗi loại đánh đổi flash, RAM, tốc độ giải mã và chất lượng hình ảnh theo cách khác nhau, và lựa chọn đúng phụ thuộc vào màn hình cùng ngân sách bộ nhớ đang nhắm tới nhiều hơn là bản thân nội dung ảnh.

Bảng so sánh GIF, PNG, JPG và ảnh C array biên dịch sẵn theo flash, RAM, tốc độ giải mã, alpha, animation và chất lượng.

Không định dạng nào thắng tuyệt đối — màn hình và ngân sách RAM quyết định lựa chọn đúng.

"Giải mã" thật ra tốn những gì

Hệ thống ảnh của LVGL tách riêng ảnh nguồn khỏi decoder biến nó thành pixel để hiển thị. Theo tài liệu image decoder của LVGL, một decoder có thể hoạt động theo nhiều cách: giải mã toàn bộ ảnh vào RAM ngay từ đầu, giải mã từng dòng khi đang vẽ, hoặc trong một số trường hợp cho phép LVGL đọc thẳng dữ liệu pixel thô mà không cần bước giải mã nào cả.

Trường hợp cuối chính là C array biên dịch sẵn: một ảnh được chuyển đổi trước thành dữ liệu pixel thô (thường là RGB565, RGB888, hoặc định dạng indexed/alpha) và lưu như một mảng const trong flash. Gần như không có bước giải mã ở runtime, nên đây là lựa chọn vẽ nhanh nhất và nhẹ RAM nhất, đổi lại là chiếm nhiều flash nhất trên mỗi pixel vì không có gì được nén cả.

Flash và RAM

Một C array thô không được nén, nên chiếm flash nhiều nhất — một ảnh RGB565 240x240 nặng khoảng 115 KB trước khi bạn viết một dòng code UI nào. PNG nén tốt với đồ họa phẳng màu, ít tông màu như khuôn mặt biểu cảm hay icon, nên cùng một hình ảnh đó thường xuống dưới nửa kích thước đó trên flash, nhưng decoder PNG của LVGL cần một buffer làm việc để giải nén, tốn RAM ngay tại thời điểm giải mã. JPG nén mạnh hơn với nội dung ảnh chụp nhưng khó đoán hơn với đồ họa phẳng kiểu hoạt hình, và decoder của nó cũng cần buffer làm việc. GIF nằm ở giữa về flash — nén dựa trên palette khá ổn với đồ họa đơn giản — nhưng chi phí thật của nó là RAM: decoder GIF của LVGL giữ một frame buffer đã giải mã đầy đủ cho khung hình animation hiện tại, có kích thước bằng đúng diện tích pixel của ảnh, và đây chính là chi tiết hay khiến một board nhỏ hết bộ nhớ nhất.

Tốc độ giải mã

C array thô: gần như miễn phí vì không có bước giải mã — LVGL chép dữ liệu pixel gần như trực tiếp. PNG: có chi phí thật nhưng thường chấp nhận được cho icon tĩnh và những lần đổi màn hình một lần, chủ yếu do bước inflate của zlib. GIF: phải trả chi phí ở mỗi khung animation, không chỉ một lần, vì mỗi khung hình phải được giải mã lại trước khi vẽ — đây là chi phí quan trọng nhất với một khuôn mặt chớp mắt hay nói liên tục. JPG: nặng nhất trong bốn loại trên thực tế. LVGL thường dùng TJpgDec để giải mã JPEG, và tài liệu tjpgd mô tả nó là một decoder gọn nhẹ được xây để chạy trong RAM hạn chế — một đánh đổi có chủ đích giữ RAM thấp nhưng để lại phần việc giải mã dựa trên DCT là chi phí chính, ổn cho một tấm ảnh thỉnh thoảng hiển thị nhưng không hợp với thứ gì phải vẽ lại mỗi khung hình.

Alpha và trong suốt

Định dạng C array thô có thể mang kênh alpha thật (ARGB8888 hoặc tương tự) nếu bạn chuyển đổi ảnh nguồn theo cách đó, cho phép blend từng pixel đầy đủ. PNG cũng hỗ trợ alpha thật, đó là lý do nó là lựa chọn tự nhiên cho icon và các thành phần UI cần nằm gọn gàng trên nền phía sau. GIF chỉ hỗ trợ trong suốt nhị phân — một chỉ số palette được đánh dấu trong suốt hoàn toàn, không có blend từng phần — đủ dùng cho một sprite đơn giản nhưng sẽ để lộ viền cứng, răng cưa thay vì mềm mại. JPG không hỗ trợ trong suốt chút nào; nó luôn là một hình chữ nhật đặc hoàn toàn.

Animation

Đây là điểm thắng rõ ràng duy nhất của GIF: nó là định dạng duy nhất trong số này có animation nhiều khung hình gắn sẵn cùng thời gian cho từng khung, và widget lv_gif của LVGL phát nó trực tiếp. PNG và JPG đều chỉ một khung hình; muốn tạo animation phải tự đổi ảnh theo timer (về cơ bản là tự xây lại thứ GIF đã làm sẵn, nhưng không có sự tiện lợi của nó) hoặc tự đổi từng khung trong một sprite sheet dạng C array. "Animation" bằng C array thô thực chất là code của bạn tự luân chuyển qua một chuỗi ảnh tĩnh — không kém phần thật, nhưng đó là logic firmware chứ không phải một định dạng asset làm hộ việc đó.

Chất lượng

C array thô và PNG đều không mất dữ liệu nếu bạn giữ đúng bit depth của ảnh nguồn — thứ bạn thiết kế ra chính là thứ hiển thị, điều này quan trọng với màu phẳng, line art sắc nét và chữ rõ. JPG là nén có mất dữ liệu và hay để lộ vết block hoặc nhòe quanh các cạnh cứng và chữ, thường thấy rõ trên kiểu biểu cảm tương phản cao, hoạt hình mà một khuôn mặt robot hay dùng. Giới hạn của GIF là bảng màu tối đa 256 màu trên mỗi khung hình; ổn với khuôn mặt đơn giản, ít màu, nhưng dễ thấy dải màu (banding) trên bất kỳ thứ gì có gradient mượt.

Chọn theo màn hình

Gợi ý định dạng cho OLED đen trắng, TFT màu nhỏ không có PSRAM, và màn hình có PSRAM.

GIF chỉ đáng tốn RAM khi bạn thật sự có PSRAM để trả cho nó.

OLED I2C đen trắng (không có PSRAM, ví dụ bản ESP32-C3 cơ bản): bỏ qua hoàn toàn việc giải mã lúc chạy. Một bitmap đen trắng nhỏ chuyển thẳng thành C array thô vừa đơn giản vừa rẻ nhất ở đây — không có RAM dư cho frame buffer của decoder GIF, và một khuôn mặt 128x64 1-bit gần như không tốn flash đáng kể. Nếu muốn chớp mắt hay animation đơn giản, hãy tự luân chuyển vài khung bitmap thô thay vì dùng một định dạng animation thật sự.

TFT màu nhỏ không có PSRAM: dựa vào PNG cho icon và các thành phần UI cần alpha mà chỉ vẽ lại thỉnh thoảng, và giữ những ảnh lớn, ít đổi (background, màn hình splash) dưới dạng C array thô để vẽ tức thì mà không ăn vào ngân sách RAM giải mã. Cẩn thận với GIF ở đây — một frame buffer giải mã đầy đủ theo độ phân giải màn hình có thể chiếm một phần lớn SRAM nội bộ của chip, nên nếu có dùng GIF động thì giữ kích thước pixel thật nhỏ.

Màn hình có PSRAM (ví dụ bản Mochi AI dùng ESP32-S3): đây là nơi GIF thật sự đáng dùng. Có PSRAM cho buffer của LVGL và bộ nhớ làm việc của decoder GIF, biểu cảm động trở nên khả thi mà không làm đói phần UI còn lại. Vẫn giữ PNG cho icon và các thành phần cần blend alpha, và để dành JPG cho đúng trường hợp nó thật sự mạnh — nội dung ảnh chụp thật — vì nó mang lại rất ít giá trị cho một màn hình toàn biểu cảm màu phẳng.

Kết luận

Không có định dạng nào "tốt nhất" trên mọi trường hợp — chỉ có định dạng phù hợp với những gì một màn hình cụ thể có thể chịu được. C array thô là lựa chọn mặc định đúng đắn khi RAM là tài nguyên khan hiếm và ảnh hiếm khi đổi. PNG xứng đáng với chi phí giải mã khi bạn cần trong suốt thật. GIF chỉ đáng bỏ RAM ra khi bạn thật sự cần animation và có PSRAM để trả cho nó. JPG vẫn nên để dành cho ảnh chụp, mà đó vốn là nhu cầu hiếm gặp trên một màn hình biểu cảm nhỏ.

Tham khảo

Đọc thêm liên quan

Chia sẻ

LinkedInFacebookX

Tiếp tục khám phá

Đọc tiếp

Bài viết liên quan

Xem thêm trong Bài viết

Nastrotek sử dụng cookie để phân tích truy cập và cá nhân hóa quảng cáo, giúp hiểu cách website được sử dụng. Bạn có thể chấp nhận hoặc từ chối các cookie không thiết yếu.