實現遠程屏幕截圖的思路很簡單,就是直接獲取設備的顯存數據,由PC再現畫 面。由於我們已經實現了Custom信道,所以我們在原有程序基礎上,增添一個 Custom_Command_Screenshots命令,就可以完成數據的獲取。但是比較麻煩的是 ,對不同的LCD設備,同樣顯示畫面,顯存數據有可能不同,對嵌入式設備,常見 的LCD顯示是16位色(也有1位或8位色的,但比較少見),簡便起見,我們僅考慮 16色顯示畫面的截圖。
16位色圖根據RGB的分量數值,一般有如下幾種模式:1555,565,555,第一 種最高位含有透明度,我們把它和555歸為一類,第二種和第三種比較常見。
對565和555來說,中間一定是G(綠色),其分量值或5位或6位,沒有什麼分 歧。麻煩的是,低5位(或高5位)有可能是紅色,也可能是藍色,如果混淆,則 顯示的畫面會出現偏色,所以必須要准確獲取該配置信息。
獲取數據和配置後,我們完全可以把獲取的數據一個點一個點地畫出來,但是 這樣做,不僅導致畫面顯示慢,還會使我們失去一次深入探究C#位圖呈現技術的 機會。
下面我們將深入研究.Net Framework的位圖顯示技術,首先聲明一個和設備顯 示尺寸一樣大小的位圖(new Bitmap)。幸運的是,我們發現可以設置 PixelFormat.Format16bppRgb565和PixelFormat.Format16bppRgb555參數。但究 竟是RGB還是BGR模式卻無法設置,如果不管這個參數,我們最終的截圖,你會發 現和顯示設備上的畫面偏色(很感謝顯示設備的默認16色模式和windows不同,否 則這個問題也許被掩蓋了)。解決方案有兩種,一是修改設備的顯示驅動(我在 開發Cortex-M3的開發板顯示驅動時,發現其顯示模式RGB/BGR是可以配置的), 二是由上位機解決這個問題。為了使程序的通用性更強,我選擇了後者。
查相關資料,我們發現在BITMAPINFOHEADER(位圖信息頭)中有一項 biCompression,其含義如下:
BI_RGB:沒有壓縮
BI_RLE8:每個象素8比特的RLE壓縮編碼,壓縮格式由2字節組成(重復象素計 數和顏色索引);
BI_RLE4:每個象素4比特的RLE壓縮編碼,壓縮格式由2字節組成
BI_BITFIELDS:每個象素的比特由指定的掩碼決定。
如果我們位圖的參數設置為PixelFormat.Format16bppRgb555模式,則 biCompression的值為BI_RGB,則我們將無法去設置RGB/BGR了,因為該結構體之 後,就是位圖數據了。
如果我們設置的參數為PixelFormat.Format16bppRgb565,則biCompression值 為BITFIELDS,BITMAPINFOHEADER結構體之後,會增加12個字節的數據,分別為4 字節的R值掩碼、4字節的G值掩碼和4字節的B值掩碼,默認數據是0xF800、0x07E0 、0x001F。
好了,我們的問題解決了,通過配置以上數據,便可解決我們的問題。
此外,該配置參數從何而來?設備的開發者一定比上層軟件開發者更清楚,所 以這個參數配置,就由我們設備上的代碼提供。
程序實現後,工作畫面如下:
一、NativeCode代碼
如下代碼均在Customprocess.cpp文件中添加。
i、新增命令
#define Custom_Command_Screenshots 0x02
ii、返回配置參數和顯存數據
case Custom_Command_Screenshots:
UINT32 ScreenDataOffset = inData[4] <<24 | inData[3] <<16 | inData[2]<<8 | inData [1];
UINT16 ScreenDataSize = inData[6] <<8 | inData[5];
if(ScreenDataOffset == 0xFFFFFFFF)
{
*outLength=6;
//555 BGR
UINT16 R_Mask = 0x001F;
UINT16 G_Mask = 0x03E0;
UINT16 B_Mask = 0x7C00;
outData[0] = (UINT8)(R_Mask & 0xFF);
outData[1] = (UINT8)((R_Mask >> 8) & 0xFF);
outData[2] = (UINT8)(G_Mask & 0xFF);
outData[3] = (UINT8)((G_Mask >> 8) & 0xFF);
outData[4] = (UINT8)(B_Mask & 0xFF);
outData[5] = (UINT8)((B_Mask >> 8) & 0xFF);
}
else
{
UINT8 *pScreen= (UINT8 *) LCD_GetFrameBuffer();
*outLength = ScreenDataSize;
memcpy(outData,(UINT8 *) (pScreen+ScreenDataOffset), ScreenDataSize);
}
break;
設備上的代碼編寫完畢,是不是很簡單?!
二、Screenshots插件開發
該部分代碼絕大部分都和《遠程文件查看器》所提到的類似,這裡就不作介紹 了,下面僅貼出最核心的圖形顯示部分的代碼。
private void palScreen_Paint(object sender, PaintEventArgs e)
{
if (LCD_BitsPerPixel == 16)
{
LCD_bmp = new Bitmap(LCD_Width, LCD_Heigth, System.Drawing.Imaging.PixelFormat.Format16bppRgb565);
MemoryStream ms = new MemoryStream ();
LCD_bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
ms.Flush();
Byte[] bytMask = new byte[4];
ms.Position = 54;
ms.Write(BitConverter.GetBytes(R_Mask), 0, 4);
ms.Write(BitConverter.GetBytes(G_Mask), 0, 4);
ms.Write(BitConverter.GetBytes(B_Mask), 0, 4);
ms.Write(LCD_Buffer, 0, LCD_Buffer.Length);
ms.Flush();
LCD_bmp = new Bitmap(ms);
//顯示的畫面是倒的,所以要翻轉一下
LCD_bmp.RotateFlip(RotateFlipType.Rotate180FlipX);
e.Graphics.DrawImage(LCD_bmp, 0, 0);
}
else //顯示紅X,表示不支持
{
e.Graphics.FillRectangle(new SolidBrush (Color.White), new Rectangle(0, 0, LCD_Width, LCD_Heigth));
e.Graphics.DrawRectangle(new Pen (Color.Red, 2), new Rectangle(0, 0, LCD_Width, LCD_Heigth));
e.Graphics.DrawLine(new Pen(Color.Red, 2), 0, 0, LCD_Width, LCD_Heigth);
e.Graphics.DrawLine(new Pen(Color.Red, 2), 0, LCD_Width, 0, LCD_Heigth);
}
}
本系列的文章,我已經草擬了若干後續文章的題目,諸如《加載文件系統中的 Pe文件》、《Pe文件探析》、《動態加載Native Code(dll)》和《實現函數回 調》等等,但是最近對uc/os-ii比較感興趣,准備把.Net MF移植到該系統上去, 以期改善.Net MF的實時性能,所以這期間,更多的可能是寫些關於uc/os-ii的文 章。