简体   繁体   中英

How to get a pixel array from TBitmap?

In a camera application bitmap pixel arrays are retrieved from a streaming camera. The pixel arrays are captured by writing them to a named pipe, where on the other end of the pipe, ffmpeg retrieves them and creates an AVI file.

I will need to create one custom frame (with custom text on), and pipe its pixels as the first frame in the resulting movie.

The question is how can I use a TBitmap (for convenience) to

  1. Create a X by Y monochrome (8 bit) bitmap from scratch, with custom text on. I want the background to be white, and the text to be black. (Mostly figured this step out, see below.)

  2. Retrieve the pixel array that I can send/write to the pipe

Step 1: The following code creates a TBitmap and writes text on it:

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());

The above basically concludes step 1.

The writing/piping code expects a byte array with the bitmaps pixels; eg

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

where pImage is a pointer to a unsigned char buffer (the bitmaps pixels), and the size is the length of this buffer.

Update: Using the generated TBitmap and a TMemoryStream for transferring data to the ffmpeg pipeline does not generate the proper result. I get a distorted image with 3 diagonal lines on it.

The buffersize for the camera frame buffers that I receive are are exactly 323736 , which is equal to the number of pixels in the image, ie 658x492. NOTE I have concluded that this 'bitmap' is not padded. 658 is not divisible by four.

The buffersize I get after dumping my generated bitmap to a memory stream, however, has the size 325798 , which is 2062 bytes larger than it is supposed to be. As @Spektre pointed out below, this discrepancy may be padding?

Using the following code for getting the pixel array;

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

So final challenge seem to be to get a bytearray that has the same size as the ones coming from the camera. How to find and remove the padding bytes from the TBitmap code??

TBitmap has a PixelFormat property to set the bit depth.

TBitmap has a HandleType property to control whether a DDB or a DIB is created. DIB is the default.

Since you are passing BMPs around between different systems, you really should be using DIBs instead of DDBs, to avoid any corruption/misinterpretation of the pixel data.

Also, this line of code:

Image1->Picture->Bitmap->Handle = bm->Handle;

Should be changed to this instead:

Image1->Picture->Bitmap->Assign(bm);
// or:
// Image1->Picture->Bitmap = bm;

Or this:

Image1->Picture->Assign(bm);

Either way, don't forget to delete bm; afterwards, since the TPicture makes a copy of the input TBitmap , it does not take ownership.

To get the BMP data as a buffer of bytes, you can use the 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() function, which outputs the pixels in DIB format. You can't obtain a byte buffer of the pixels for a DDB, since they depend on the device they are rendered to. DDBs are only usable in-memory in conjunction with HDC s, you can't pass them around. But you can convert a DIB to a DDB once you have a final device to render it to.

In other words, get the pixels from the camera, save them to a DIB, pass that around as needed (ie, over the pipe), and then do whatever you need with it - save to a file, convert to DDB to render onscreen, etc.

This is just an addon to existing answer (with additional info after the OP edit)

Bitmap file-format has align bytes on each row (so there usually are some bytes at the end of each line that are not pixels) up to some ByteLength (present in bmp header). Those create the skew and diagonal like lines. In your case the size discrepancy is 4 bytes per row:

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

but beware the align size depends on image width and bmp header ...

Try this instead:

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

mixing GDI winapi calls for pixel array access (bitblt etc...) with VCL bmDIB bitmap might cause problems and resource leaks (hence the error on exit) and its also slower then usage of ScanLine[] (if coded right) so I strongly advice to use native VCL functions (as I did in above example) instead of the GDI/winapi calls where you can.

for more info see:

Also you mention your image source is camera. If you use pf8bit it mean its palette indexed color which is relatively slow and ugly if native GDI algo is used (to convert from true/hi color camera image) for better transform see:

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM