如何从 TBitmap 获取像素数组?

How to get a pixel array from TBitmap?

在相机应用程序中,位图像素阵列是从流式相机中检索的。 像素阵列通过将它们写入命名管道来捕获,在管道的另一端,ffmpeg 检索它们并创建一个 AVI 文件。

我需要创建一个自定义帧(启用自定义文本),并将其像素作为生成影片的第一帧。

问题是如何使用 TBitmap(为方便起见)

  1. 从头开始创建 X x Y 单色(8 位)位图,使用 自定义文本。我希望背景为白色,文字为 变黑。 (主要是想出了这一步,见下文。)

  2. 检索我可以send/write到管道的像素数组

第 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基本结束

writing/piping 代码需要一个包含位图像素的字节数组;例如

unsigned long numWritten;
WriteFile(mPipeHandle, pImage, size, &numWritten, NULL);

其中 pImage 是指向无符号字符缓冲区(位图像素)的指针,大小是该缓冲区的长度。

更新: 使用生成的 TBitmap 和 TMemoryStream 将数据传输到 ffmpeg 管道不会生成正确的结果。我得到一张扭曲的图像,上面有 3 条对角线。

我收到的相机帧缓冲区的缓冲区大小正好是 323736,它等于图像中的像素数,即 658x492。 注意 我得出结论,这个 'bitmap' 没有填充。 658 不能被 4 整除。

然而,我将生成的位图转储到内存流后得到的缓冲区大小为 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,以避免任何 corruption/misinterpretation 像素数据。

另外,这行代码:

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() method, saving to a TMemoryStream. Or, if you just want the pixel data, not the complete BMP data (ie, without BMP headers - see Bitmap Storage), you can use the Win32 GetDiBits() 函数,它以 DIB 格式输出像素。您无法为 DDB 获取像素的字节缓冲区,因为它们取决于它们被渲染到的设备。 DDB 只能在内存中与 HDC 结合使用,您不能传递它们。但是一旦你有一个最终设备来渲染它,你就可以将 DIB 转换为 DDB。

换句话说,从相机获取像素,将它们保存到 DIB,根据需要传递它(即通过管道),然后用它做任何你需要的事——保存到文件,转换到 DDB 以在屏幕上呈现等

这只是对现有答案的补充(在 OP 编辑​​后有附加信息)

Bitmap file-format 在每一行上都有对齐字节(因此每行末尾通常有一些字节不是像素),直到某个 ByteLength(存在于 bmp header 中)。那些创建倾斜和对角线一样的线。在您的情况下,大小差异是每行 4 个字节:

(xs + align)*ys  + header = size
(658+     4)*492 + 94     = 325798

但要注意对齐大小取决于图像宽度和 bmp header ...

试试这个:

    // 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;

将用于像素数组访问(bitblt 等...)的 GDI winapi 调用与 VCL bmDIB 位图混合可能会导致问题和资源泄漏(因此在退出时出错)并且它的使用速度也比 ScanLine[](如果编码正确)所以我强烈建议使用本机 VCL 函数(就像我在上面的示例中所做的那样)而不是尽可能使用 GDI/winapi 调用。

有关详细信息,请参阅:

你还提到你的图片来源是相机。如果你使用 pf8bit 这意味着它的调色板索引颜色相对缓慢且丑陋,如果使用原生 GDI 算法(从 true/hi 彩色相机图像转换)更好的变换见: