[英]How to get a pixel array from TBitmap?
在相機應用程序中,位圖像素陣列是從流相機中檢索的。 像素數組是通過將它們寫入命名管道來捕獲的,在管道的另一端,ffmpeg 檢索它們並創建一個 AVI 文件。
我將需要創建一個自定義幀(帶有自定義文本),並將其像素作為結果電影中的第一幀。
問題是我如何使用 TBitmap(為方便起見)來
從頭開始創建 X x Y 單色(8 位)位圖,並啟用自定義文本。 我希望背景為白色,文本為黑色。 (主要是想出了這一步,見下文。)
檢索我可以發送/寫入管道的像素陣列
步驟 1:以下代碼創建一個 TBitmap 並在其上寫入文本:
int w = 658;
int h = 492;
TBitmap* bm = new TBitmap();
bm->Width = w;
bm->Height = h;
bm->HandleType = bmDIB;
bm->PixelFormat = pf8bit;
bm->Canvas->Font->Name = "Tahoma";
bm->Canvas->Font->Size = 8;
int textY = 10;
string info("some Text");
bm->Canvas->TextOut(10, textY, info.c_str());
以上基本上完成了步驟1。
寫入/管道代碼需要一個帶有位圖像素的字節數組; 例如
unsigned long numWritten;
WriteFile(mPipeHandle, pImage, size, &numWritten, NULL);
其中 pImage 是指向無符號字符緩沖區(位圖像素)的指針,大小是該緩沖區的長度。
更新:使用生成的 TBitmap 和 TMemoryStream 將數據傳輸到 ffmpeg 管道不會生成正確的結果。 我得到一個扭曲的圖像,上面有 3 條對角線。
我收到的相機幀緩沖區的緩沖區大小正好是323736 ,它等於圖像中的像素數,即 658x492。 注意我已經得出結論,這個“位圖”沒有被填充。 658 不能被四整除。
但是,將生成的位圖轉儲到內存流后得到的 buffersize 大小為325798 ,比預期大2062字節。 正如@Spektre 在下面指出的,這種差異可能是填充?
使用以下代碼獲取像素數組;
ByteBuffer CustomBitmap::getPixArray()
{
// --- Local variables --- //
unsigned int iInfoHeaderSize=0;
unsigned int iImageSize=0;
BITMAPINFO *pBitmapInfoHeader;
unsigned char *pBitmapImageBits;
// First we call GetDIBSizes() to determine the amount of
// memory that must be allocated before calling GetDIB()
// NB: GetDIBSizes() is a part of the VCL.
GetDIBSizes(mTheBitmap->Handle,
iInfoHeaderSize,
iImageSize);
// Next we allocate memory according to the information
// returned by GetDIBSizes()
pBitmapInfoHeader = new BITMAPINFO[iInfoHeaderSize];
pBitmapImageBits = new unsigned char[iImageSize];
// Call GetDIB() to convert a device dependent bitmap into a
// Device Independent Bitmap (a DIB).
// NB: GetDIB() is a part of the VCL.
GetDIB(mTheBitmap->Handle,
mTheBitmap->Palette,
pBitmapInfoHeader,
pBitmapImageBits);
delete []pBitmapInfoHeader;
ByteBuffer buf;
buf.buffer = pBitmapImageBits;
buf.size = iImageSize;
return buf;
}
所以最后的挑戰似乎是獲得一個與來自相機的字節數組具有相同大小的字節數組。 如何從 TBitmap 代碼中查找和刪除填充字節?
TBitmap
有一個PixelFormat
屬性來設置位深度。
TBitmap
有一個HandleType
屬性來控制是創建 DDB 還是 DIB。 DIB 是默認設置。
由於您在不同系統之間傳遞 BMP,您確實應該使用 DIB 而不是 DDB,以避免像素數據的任何損壞/誤解。
另外,這行代碼:
Image1->Picture->Bitmap->Handle = bm->Handle;
應該改為這樣:
Image1->Picture->Bitmap->Assign(bm);
// or:
// Image1->Picture->Bitmap = bm;
或這個:
Image1->Picture->Assign(bm);
無論哪種方式,不要忘記delete bm;
之后,由於TPicture
制作了輸入TBitmap
的副本,因此它不會獲得所有權。
要將 BMP 數據作為字節緩沖區獲取,您可以使用TBitmap::SaveToStream()
方法,保存到TMemoryStream
。 或者,如果您只想要像素數據,而不是完整的 BMP 數據(即,沒有 BMP 標頭 - 請參閱位圖存儲),您可以使用 Win32 GetDiBits()
函數,該函數以 DIB 格式輸出像素。 您無法獲得 DDB 像素的字節緩沖區,因為它們取決於呈現它們的設備。 DDB 只能在內存中與HDC
結合使用,您不能傳遞它們。 但是,一旦您擁有要渲染的最終設備,就可以將 DIB 轉換為 DDB。
換句話說,從相機獲取像素,將它們保存到 DIB,根據需要傳遞它(即通過管道),然后用它做任何你需要的 - 保存到文件,轉換為 DDB 以在屏幕上呈現, 等等。
這只是現有答案的一個插件(在 OP 編輯后有附加信息)
位圖文件格式在每行上都有對齊字節(因此每行末尾通常有一些不是像素的字節)直到某個 ByteLength(存在於 bmp 標頭中)。 那些創造了傾斜和對角線一樣的線條。 在您的情況下,大小差異為每行 4 個字節:
(xs + align)*ys + header = size
(658+ 4)*492 + 94 = 325798
但要注意對齊大小取決於圖像寬度和 bmp 標題...
試試這個:
// create bmp
Graphics::TBitmap *bmp=new Graphics::TBitmap;
// bmp->Assign(???); // a) copy image from ???
bmp->SetSize(658,492); // b) in case you use Assign do not change resolution
bmp->HandleType=bmDIB;
bmp->PixelFormat=pf8bit;
// bmp->Canvas->Draw(0,0,???); // b) copy image from ???
// here render your text using
bmp->Canvas->Brush->Style=bsSolid;
bmp->Canvas->Brush->Color=clWhite;
bmp->Canvas->Font->Color=clBlack;
bmp->Canvas->Font->Name = "Tahoma";
bmp->Canvas->Font->Size = 8;
bmp->Canvas->TextOutA(5,5,"Text");
// Byte data
for (int y=0;y<bmp->Height;y++)
{
BYTE *p=(BYTE*)bmp->ScanLine[y]; // pf8bit -> BYTE*
// here send/write/store ... bmp->Width bytes from p[]
}
// Canvas->Draw(0,0,bmp); // just renfder it on Form
delete bmp; bmp=NULL;
將像素陣列訪問的 GDI winapi 調用(bitblt 等...)與 VCL bmDIB 位圖混合可能會導致問題和資源泄漏(因此退出時出現錯誤),並且它的使用速度也比使用ScanLine[]
(如果編碼正確)慢,所以我強烈建議盡可能使用本機 VCL 函數(如我在上面的示例中所做的那樣)而不是 GDI/winapi 調用。
有關更多信息,請參閱:
您還提到您的圖像來源是相機。 如果您使用pf8bit
這意味着它的調色板索引顏色相對緩慢和丑陋,如果使用原生GDI算法(從真/高彩色相機圖像轉換)以獲得更好的轉換,請參見:
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.