[英]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.