J2ME (MIDP) 中 Canvas 無直接獲取像素數據的 API,無法直接將屏幕內容保存為 Image。本文介紹經典離屏緩衝 (Off-screen Buffer / 雙緩衝)技術:將所有繪圖操作重定向到一個內存 Image 上,繪製完成後再一次性繪製到屏幕 Graphics。此技術同時解決了屏幕捕獲與閃爍消除雙重需求。
標準繪圖流程: 雙緩衝捕獲流程:
paint(Graphics g) paint(Graphics g)
│ │
▼ ▼
g.drawXxx(...) 1. 創建/獲取 offImage = Image.createImage(w, h)
│ 2. Graphics offG = offImage.getGraphics()
▼ 3. 所有繪圖操作針對 offG: offG.drawXxx(...)
屏幕顯示 4. 繪製完成後: g.drawImage(offImage, 0, 0, TOP|LEFT)
5. 此刻 offImage 即為完整幀內容,可隨時返回/保存
import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;
public class ScreenCaptureMIDlet extends MIDlet implements CommandListener {
private Display display;
private CaptureCanvas canvas;
private Command cmdCapture, cmdExit;
protected void startApp() {
if (display == null) display = Display.getDisplay(this);
canvas = new CaptureCanvas(true); // 啟用雙緩衝
cmdCapture = new Command("捕獲屏幕", Command.SCREEN, 1);
cmdExit = new Command("退出", Command.EXIT, 2);
canvas.addCommand(cmdCapture);
canvas.addCommand(cmdExit);
canvas.setCommandListener(this);
display.setCurrent(canvas);
}
protected void pauseApp() {}
protected void destroyApp(boolean unconditional) {}
public void commandAction(Command c, Displayable d) {
if (c == cmdCapture) {
Image captured = canvas.captureScreen();
if (captured != null) {
Form form = new Form("捕獲結果");
form.append(captured);
// 可選:保存到 RMS 或發送給服務器
display.setCurrent(form);
}
} else if (c == cmdExit) {
notifyDestroyed();
}
}
}
class CaptureCanvas extends Canvas {
private final boolean buffered;
private Image offImage; // 離屏緩衝圖像
private int width, height;
public CaptureCanvas(boolean buffered) {
this.buffered = buffered;
this.width = getWidth();
this.height = getHeight();
if (buffered) {
offImage = Image.createImage(width, height); // 創建離屏緩衝
}
}
protected void paint(Graphics g) {
// 1. 背景
g.setColor(0xFFFFFF);
g.fillRect(0, 0, width, height);
g.setColor(0x000000);
// 2. 如果啟用雙緩衝,繪圖目標切換到 offImage
Graphics target = g;
if (buffered && offImage != null) {
target = offImage.getGraphics();
}
// 3. 實際繪圖業務邏輯 (針對 target)
target.setColor(0x808000);
target.fillRoundRect((width-100)/2, (height-60)/2, 100, 60, 5, 3);
target.setColor(0x0000FF);
target.drawString("J2ME 雙緩衝演示", (width-120)/2, (height-20)/2, Graphics.TOP|Graphics.HCENTER);
// 4. 如果使用雙緩衝,將離屏圖像一次性繪製到屏幕
if (buffered && offImage != null) {
g.drawImage(offImage, 0, 0, Graphics.TOP|Graphics.LEFT);
}
}
/** 公開方法:獲取當前屏幕內容 (即離屏緩衝內容) */
public Image captureScreen() {
return offImage; // 直接返回離屏 Image
}
}
| 要點 | 說明 | 注意事項 |
|---|---|---|
| Image.createImage(w,h) | 創建可變像素的離屏圖像,支持 getGraphics() | 尺寸需與 Canvas 尺寸一致 (getWidth/getHeight) |
| Graphics 切換 | 所有 draw/fill/setColor/setFont 等操作針對 offImage.getGraphics() | 字體、顏色、剪輯區域等狀態需同步設置 |
| 一次性提交 | g.drawImage(offImage, 0, 0, TOP|LEFT) 將整幀拷貝到屏幕 | 避免逐像素操作屏幕 Graphics,消除閃爍 |
| 捕獲時機 | paint() 完成後 offImage 即為最新幀 | 可在 paint 結束時賦值給 volatile 字段供外部讀取 |
| 內存佔用 | 一幀圖像約 width*height*2 bytes (RGB565) | 176x208 屏 ≈ 70KB,多幀緩衝需注意堆限制 |
| 透明度支持 | MIDP 2.0+ 支持 ARGB 圖像 (createImage w/ alpha) | 需檢設備支持:Image.createImage(w,h,0xFFFF0000) 測試 |
sizeChanged(w,h) 中重新創建 offImageImage.createImage(Image src) 創建不可變副本,無法 getGraphics(),務必用 createImage(w,h)drawImage(x,y,anchor) 錨點常用 TOP|LEFT 對齊左上角createImage(w,h) 或堆極小,需優雅降級 (關閉雙緩衝、降低分辨率)J2ME 雙緩衝技術通過離屏 Image 承載所有繪圖操作,一舉實現屏幕內容捕獲與消除繪圖閃爍。核心模式:paint() → 切換 Graphics 目標 → 業務繪圖 → 統一提交屏幕。雖然 J2ME 已退出歷史舞臺,但此「離屏渲染 + 統一提交」模式在 Android SurfaceView、Web OffscreenCanvas、現代 GPU 渲染管線中仍是核心架構思想。