1

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; e.g.

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, i.e. 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??

Totte Karlsson
  • 1,261
  • 1
  • 20
  • 55

2 Answers2

3

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 HDCs, 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.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • hmm `bmDIB` is default? They changed it? I remember from BCB5 times that without setting `Graphics::TBitmap->HandleType` to `bmDIB` the `ScanLine[]` property would throw access violations ... Or by setting it again the bmp is recomputed somehow to allow `ScanLine[]` access? – Spektre Jan 16 '20 at 08:30
  • @RemeLebeau Thanks for your info. My initial question was confused about DIB's and DDB's. I changed so the TBitmap is now a DIB and I can properly create and write text to it. Saving it to a file confirms that. However, getting the pixels as a Bytearray seem to be the problem. The getPixArray code above fails when the TBitmap is DIB for some reason! – Totte Karlsson Jan 17 '20 at 00:32
  • @Spektre I don't know, maybe I'm wrong about the default `HandleType`, it might be `bmDDB` after all. `bmDIB` is 0 and `bmDDB` is 1, and `HandleType` has `nodefault` on it. But looking at the `HandleType` getter, I see it is actually calculated based on several criteria, so it could go either way, I suppose. – Remy Lebeau Jan 17 '20 at 00:38
  • @TotteKarlsson use `ScanLine[y]` property ... you can access pixels for read and write by it ... and if you store the values into your array of pointers once then its also fast see [#4. GDI Bitmap](https://stackoverflow.com/a/21699076/2521214) You just need to use variable type with the same length as your pixel format. – Spektre Jan 17 '20 at 07:27
1

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:

Spektre
  • 49,595
  • 11
  • 110
  • 380
  • The flow of bitmaps from the camera to the AVI is (as far as I understand now) is using DIB's. This chain of my code works very good. My current problem is the generation of a single individual, custom, bitmap to put as the first frame in the AVI. This bitmap will have custom text on it. Since its only one frame, efficiency does not matter that much. So it sounds that the bitmaps generated by TBitmap has different padding than the one from the camera, as the pixel arrays have different size for the same resolution? – Totte Karlsson Jan 17 '20 at 17:50
  • @TotteKarlsson without sample data I can only guess ... my bet is that your camera is giving you raw image instead of a bitmap ... – Spektre Jan 17 '20 at 23:56
  • You are correct about that! I was able to copy the Pixelbuffer, skipping the padding bytes, and it works perfect! That is, the camera's pixels are delivered with no padding bytes. – Totte Karlsson Jan 19 '20 at 22:07