-2

from a third party component I am receiving a PBitmap which is a pointer to Windows.tagBitmap record.

{ Bitmap Header Definition }
  PBitmap = ^TBitmap;
  {$EXTERNALSYM tagBITMAP}
  tagBITMAP = record
    bmType: Longint;
    bmWidth: Longint;
    bmHeight: Longint;
    bmWidthBytes: Longint;
    bmPlanes: Word;
    bmBitsPixel: Word;
    bmBits: Pointer;
  end;
  TBitmap = tagBITMAP;
  {$EXTERNALSYM TBitmap}
  BITMAP = tagBITMAP;
  {$EXTERNALSYM BITMAP}

I would like to convert data contained in this pointer to a regular DIB and save this data to a stream. Just as Graphics.TBitmap.SaveToStream does. So preferably I would like to have a procedure like:

procedure SavetagBitmapAsDIBToStream(const ABitmap: PBitmap; var AStream: TStream);

I've tried to find information about this structure on MSDN, but none of the headers described there (BITMAPFILEHEADER, BITMAPINFOHEADER etc.) seems to conform tagBITMAP.

Could someone experienced in the matter could help me?

edited: An example in in C \ C++ would also be fine for me.

Wodzu
  • 6,932
  • 10
  • 65
  • 105
  • 1
    The Microsoft documentation on that structure can be found on this MSDN page: https://msdn.microsoft.com/en-us/library/dd183371(v=vs.85).aspx – blong Sep 30 '16 at 09:42
  • Thank you, and how can I "unpack" it to DIB? – Wodzu Sep 30 '16 at 09:44
  • Open VCL sources and check the procedure `VCL.Graphics.TBitmap.ReadDIB` - it is not EXACTLY what you need (because it takes pre-defined size for example ) but it is MOSTLY there. You can do your own prser from it – Arioch 'The Sep 30 '16 at 10:40
  • 1
    you might also use Win32 CreateBitmap function to create Windows GDI handle to that tagBITMAP data and then assign that Handle to `Vcl.Graphics.TBitmap.Handle` and save TBitmap to file/stream/whatever – Arioch 'The Sep 30 '16 at 10:52
  • I don't see how ReadDIB is operating on tagBITMAP it uses tagBITMAPFILEHEADER ? I would like to save it directly to stream, without the help of the TBitmap due to performance reasons. – Wodzu Sep 30 '16 at 11:22
  • read it through, it does in XE2, as part of parsing DIBSection / if you want to make it non-VCL then better just do it, drop Delphi tag and ask general Win32 development community, they would probably end up with C++ code, but it is not hard to translate it to Delphi – Arioch 'The Sep 30 '16 at 11:28
  • If you are talking about the: procedure `TBitmap.ReadDIB(Stream: TStream; ImageSize: LongWord; bmf: PBitmapFileHeader);` then I don't see it (in Delphi XE5) – Wodzu Sep 30 '16 at 11:40
  • 1
    I don't understand the question. You already have the layout for the `BITMAP` structure (`tagBITMAP` is just the internal name, an artifact of early C, see [Why do structures get tag names even if there is a typedef?](https://blogs.msdn.microsoft.com/oldnewthing/20080327-00/?p=22973)). A [DIB](https://msdn.microsoft.com/en-us/library/dd183562.aspx), on the other hand, isn't really anything special. It's just a regular bitmap with certain features. So what problem are you really trying to solve here? – IInspectable Sep 30 '16 at 11:46
  • @IInspectable I need to convert tagBitmap to [this](https://msdn.microsoft.com/en-us/library/windows/desktop/dd183391(v=vs.85).aspx) structure to be able to process it further. I want to have in memory a layout of data the same as it would be stored in the file. – Wodzu Sep 30 '16 at 11:53
  • 1
    That's a bitmap, not necessarily a DIB. I still don't understand the question. – IInspectable Sep 30 '16 at 11:57
  • "then I don't see it (in Delphi XE5) " - what does it mean ??? PS. In XE2 AFAIR it des not have bmf parameter, but that does not matter – Arioch 'The Sep 30 '16 at 12:09
  • @Arioch'The I mean, that I dont see any reference to tagBITMAP in the implementation of TBitmap.ReadDIB in Delphi XE5. – Wodzu Sep 30 '16 at 12:12
  • As I mentioned already, `tagBITMAP` is an **internal** symbol. The symbol used by code is `BITMAP` (see `{$EXTERNALSYM BITMAP}`). – IInspectable Sep 30 '16 at 12:16
  • @IInspectable Yes I understand, there no reference to `BITMAP` either. – Wodzu Sep 30 '16 at 12:19
  • 1
    I still don't understand the question. It sounds like you are asking about your solution, instead of the problem you are really trying to solve (and mixing up unrelated concepts along the way). I'd suggest that you ask about your problem instead. – IInspectable Sep 30 '16 at 12:23

2 Answers2

2

Use the Win32 API CreateBitmapIndirect() function to create a DDB HBITMAP handle from your tagBITMAP structure, then assign that HBITMAP to the Handle property of a VCL TBitmap object and save the object to your TStream (it will save it as a DIB).

uses
  Winapi.Windows, Vcl.Graphics;

procedure SavetagBitmapAsDIBToStream(const ABitmap: PBitmap; var AStream: TStream);
var
  Bmp: Vcl.Graphics.TBitmap;
begin
  Bmp := Vcl.Graphics.TBitmap.Create;
  try
    Bmp.Handle := CreateBitmapIndirect(ABitmap);
    Bmp.HandleType := bmDIB; // optional
    Bmp.SaveToStream(AStream);
  finally
    Bmp.Free;
  end;
end;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks Remy, your function does the job. However, I would like to avoid usage of the `Graphics.TBitmap` due to performance reasons. I would like to save directly to the stream. If no better answer comes up, I will accpet this as an answer. – Wodzu Oct 01 '16 at 21:03
  • Then you will have to manually process the DDB and create a DIB from it. Look at the source code for `TBitmap` in `Vcl.Graphics.pas`, specifically at the `TBitmap.DIBNeeded()` and `TBitmap.SaveToStream()` methods, and the `CopyBitmap()` function. A DIB is created via `CreateDIBSection()` using information extracted from the DDB, and then data is extracted from the DIB and written to the destination `TStream` using `BITMAPFILEHEADER`, `BITMAPCOREHEADER`, `DIBSECTION`, `RGBQUAD`, etc. – Remy Lebeau Oct 03 '16 at 18:15
1

Here is a draft of the solution. It should help someone to built a proper one with error handling / prettier code etc.

function CreateBitmapInfoStruct(pBmp: PBitmap): TBitmapInfo;
var
  bmi: TBitmapInfo;
  cClrBits: Word;
begin
  cClrBits := pBmp.bmPlanes * pBmp.bmBitsPixel;
  if (cClrBits = 1) then
      cClrBits := 1
  else if (cClrBits <= 4) then
      cClrBits := 4
  else if (cClrBits <= 8) then
      cClrBits := 8
  else if (cClrBits <= 16) then
      cClrBits := 16
  else if (cClrBits <= 24) then
      cClrBits := 24
  else cClrBits := 32;

  bmi.bmiHeader.biSize := sizeof(BITMAPINFOHEADER);
  bmi.bmiHeader.biWidth := pBmp.bmWidth;
  bmi.bmiHeader.biHeight := pBmp.bmHeight;
  bmi.bmiHeader.biPlanes := pBmp.bmPlanes;
  bmi.bmiHeader.biBitCount := pBmp.bmBitsPixel;
  if (cClrBits < 24) then
      bmi.bmiHeader.biClrUsed := (1 shl cClrBits)
  else
    bmi.bmiHeader.biClrUsed := 0;

  bmi.bmiHeader.biCompression := BI_RGB;
  bmi.bmiHeader.biSizeImage := ((bmi.bmiHeader.biWidth * cClrBits + 31) and (not 31)) div 8
      * bmi.bmiHeader.biHeight;
  bmi.bmiHeader.biClrImportant := 0;
  Result := bmi;
end;

procedure SavetagBitmapAsDIBToStream(const ABitmap: PBitmap; AStream: TStream);
var
  pbi: TBitmapInfo;
  lHDC: HDC;
  pbih: BITMAPINFOHEADER ;
  hdr: BITMAPFILEHEADER;
  lpBits: PByte;
  hBMP: HBITMAP;
begin
  pbi := CreateBitmapInfoStruct(ABitmap);
  lHDC := CreateCompatibleDC(0);
  GetMem(lpBits, pbih.biSizeImage);
  hBmp := CreateBitmapIndirect(ABitmap^);
  try
    pbih := pbi.bmiHeader;
    GetDIBits(lHDC, hBMP, 0, pbih.biHeight, lpBits, pbi, DIB_RGB_COLORS);
    hdr.bfType := $4d42;
    hdr.bfSize := sizeof(BITMAPFILEHEADER) + pbih.biSize + pbih.biClrUsed
          * sizeof(RGBQUAD) + pbih.biSizeImage;
    hdr.bfReserved1 := 0;
    hdr.bfReserved2 := 0;
    hdr.bfOffBits := sizeof(BITMAPFILEHEADER) +
        pbih.biSize + pbih.biClrUsed
        * sizeof (RGBQUAD);

    AStream.Write(hdr, SizeOf(BITMAPFILEHEADER));
    AStream.Write(pbih, SizeOf(BITMAPINFOHEADER) + pbih.biClrUsed * SizeOf(RGBQUAD));
    AStream.Write(lpBits^, pbih.biSizeImage);
  finally
    FreeMem(lpBits);
    DeleteObject(hBMP);
    ReleaseDC(0, lHDC);
  end;
end;

Thanks Remy for help and thanks for downvotes to my question. Keep them pouring! :)

Wodzu
  • 6,932
  • 10
  • 65
  • 105