Recent

Author Topic: [SOLVED] Is TBGRABitmap.Data always contiguous?  (Read 9186 times)

del

  • Sr. Member
  • ****
  • Posts: 258
[SOLVED] Is TBGRABitmap.Data always contiguous?
« on: June 14, 2021, 06:26:58 am »
I normally use TBGRABitmap for image I/O and do all the pixel operations on an array of single. Normally I access the pixels of the TBGRABitmap using Scanline and PBGRAPixel. But I wanted to see if Data could behave like a contiguous array of bytes. So I commented out the Scanline code and replaced it with byte array code. MImg is my image class and "m_" are class variables. I have other code that converts the floating point array (m_imP) back into a TBGRABitmap and displays it. When I tested this code it worked exactly like the standard Scanline / PGBRAPixel method. The loaded image displayed perfectly when it went thru the sequence: Image file -> TBGRABitmap -> pointer to byte -> pointer to single -> then back to TBGRABitmap for display on a form. Being able to access TBGRABitmap.Data directly as a byte array will come in handy. Is it always contiguous?

Code: Pascal  [Select][+][-]
  1. constructor MImg.Create(fileName: string);
  2. type
  3.   imgbytes = ^byte;
  4.  
  5. var
  6.   i, j, k, totPix, off, offP, pixP, strideP: integer;
  7.   pixData: imgbytes;
  8.   inImg: TBGRABitmap;
  9.  
  10. begin
  11.   inImg := TBGRABitmap.Create(fileName);
  12.   m_rows := inImg.Height;
  13.   m_cols := inImg.Width;
  14.   m_bands := 3;
  15.   m_pix := m_bands;
  16.   m_stride := m_pix * m_cols;
  17.   pixP := 4;
  18.   strideP := pixP * m_cols;
  19.  
  20.   totPix := m_rows * m_cols;
  21.   GetMem(pixData, totPix * pixP * SizeOf(byte));
  22.   Move(inImg.Data^, pixData^, totPix * pixP * SizeOf(byte));
  23.  
  24.   GetMem(m_imP, totPix * m_pix * SizeOf(single));
  25.  
  26.   for i := 0 to m_rows - 1 do
  27.   begin
  28.     off := i * m_stride; // skips i * m_cols * 3
  29.     offP := i * strideP; // skips i * m_cols * 4
  30.  
  31.     for j := 0 to m_cols - 1 do
  32.     begin
  33.       for k := 0 to 2 do
  34.       begin
  35.         m_imP[off + k] := pixData[offP + k];
  36.       end;
  37.  
  38.       off += m_pix; // skips 3
  39.       offP += pixP; // skips 4
  40.     end;
  41.   end;
  42.  
  43.   inImg.Free;
  44.   FreeMem(pixData);
  45. end;
« Last Edit: June 14, 2021, 09:45:27 am by del »

af0815

  • Hero Member
  • *****
  • Posts: 1284
Re: Is TBGRABitmap.Data always contiguous?
« Reply #1 on: June 14, 2021, 07:17:11 am »
Yes BGRABitmap.data can accessed stable as bytes, across the systembounderies (Win, Linux,..)

See some samples here https://github.com/afriess/GSTCamera/tree/master/pasv4l/demos. I have got troubles wit 3-Byte and 4-Byte Bitmaps, BGRABitmap was for me the fastest soloution.
regards
Andreas

del

  • Sr. Member
  • ****
  • Posts: 258
Re: Is TBGRABitmap.Data always contiguous?
« Reply #2 on: June 14, 2021, 09:44:11 am »
Yes BGRABitmap.data can accessed stable as bytes, across the systembounderies (Win, Linux,..)

See some samples here https://github.com/afriess/GSTCamera/tree/master/pasv4l/demos. I have got troubles wit 3-Byte and 4-Byte Bitmaps, BGRABitmap was for me the fastest soloution.
Thanks. I see from your code and a test I did that TBitmap.RawImage.Data is also a contiguous array of bytes. I can probably put that characteristic to good use as well.
« Last Edit: June 14, 2021, 10:17:41 am by del »

af0815

  • Hero Member
  • *****
  • Posts: 1284
Re: [SOLVED] Is TBGRABitmap.Data always contiguous?
« Reply #3 on: June 14, 2021, 01:07:46 pm »
Handle TBitmap.RawImage.Data with care. I have troubles to hold it stable between the differnt OS. Win-32, win-64, Linux-X64, Linux-arm. BGRABitmap was more stable for me.
regards
Andreas

winni

  • Hero Member
  • *****
  • Posts: 3197
Re: [SOLVED] Is TBGRABitmap.Data always contiguous?
« Reply #4 on: June 14, 2021, 01:33:13 pm »

 TBitmap.RawImage.Data belongs to the realm of the OS,
 TBGRABitmap.RawImage.Data belongs to BGRAbitmap.

So with  TBitmap.RawImage.Data you can never be shure unless you know exactly what you are doing.

Winni

del

  • Sr. Member
  • ****
  • Posts: 258
Re: [SOLVED] Is TBGRABitmap.Data always contiguous?
« Reply #5 on: June 14, 2021, 03:18:30 pm »
Got it. What I'm looking for is an "RGB" or "BGR" version of "TBGRABitmap". I use the "A" channel on occasion, but it's extra "freight" for most of what I do. Which is video frames. Maybe TBGRABitmap has a 24 bit mode?

Edit: It looks like TBitmap.RawImage.Data is column-padded to be a multiple of 4, which means it's not really guaranteed to be contiguous. So I'd have to adjust the stride. IIRC CImage (MFC) had some wrinkle like this.
« Last Edit: June 14, 2021, 04:37:21 pm by del »

winni

  • Hero Member
  • *****
  • Posts: 3197
Re: [SOLVED] Is TBGRABitmap.Data always contiguous?
« Reply #6 on: June 14, 2021, 05:28:17 pm »
Hi!

I never tried that, but I know:

The unit GraphType contains the TRawImageDescription.
This  contains the the byte field AlphaPrec. If it is set to 0 this means Alpha is unused.

If you set that the you can misstreat the Alpha channel for other use.

I hope it works, but I don't know.

Winni

del

  • Sr. Member
  • ****
  • Posts: 258
Re: [SOLVED] Is TBGRABitmap.Data always contiguous?
« Reply #7 on: June 15, 2021, 06:40:00 am »
Hi!

I never tried that, but I know:

The unit GraphType contains the TRawImageDescription.
This  contains the the byte field AlphaPrec. If it is set to 0 this means Alpha is unused.

If you set that the you can misstreat the Alpha channel for other use.

I hope it works, but I don't know.

Winni
Thanks. You gave me some ideas. I've been fighting with GTK all day (Linux Manjaro). It refuses to deal with anything that doesn't have an alpha channel. It wouldn't even let me create a TBitmap with BPP of 24. With one exception: if I created the TBitmap by LoadFromFile.

When I went deep enough and forced the TBitmap to be BPP 24 I got this error: "Msg = 'TGtk2WidgetSet.CreateBitmapFromRawImage Incompatible BitsPerPixel' var Msg: string /usr/lib/lazarus/components/".

At any rate I learned about other pascal image classes and can check them out for 24 BPP I/O. And for display on a form I just write to its TBitmap directly (dispBmp is a TImage.Picture.Bitmap inside a scroll box). Heap trace showed no leaks.

Code: Pascal  [Select][+][-]
  1. procedure MImg.Display(dispBmp: TBitmap);
  2. var
  3.   i, j, k, off, offP, strideP, pixP: integer;
  4.  
  5. begin
  6.   dispBmp.BeginUpdate;
  7.   dispBmp.SetSize(m_cols, m_rows);
  8.   pixP := dispBmp.RawImage.Description.BitsPerPixel div 8;
  9.   strideP := dispBmp.RawImage.Description.BytesPerLine;
  10.  
  11.   for i := 0 to m_rows - 1 do
  12.   begin
  13.     off := i * m_stride;
  14.     offP := i * strideP;
  15.  
  16.     for j := 0 to m_cols - 1 do
  17.     begin
  18.       for k := 0 to 2 do
  19.       begin
  20.         dispBmp.RawImage.Data[offP + k] := Round(m_imP[off + k]); // m_imP is ^single
  21.       end;
  22.  
  23.       off += m_pix;
  24.       offP += pixP;
  25.     end;
  26.   end;
  27.  
  28.   dispBmp.EndUpdate;
  29. end;
« Last Edit: June 15, 2021, 08:41:48 am by del »

del

  • Sr. Member
  • ****
  • Posts: 258
Re: [SOLVED] Is TBGRABitmap.Data always contiguous?
« Reply #8 on: June 16, 2021, 05:05:51 am »
OK I'll wrap this up. TBGRABitmap looks like it's the way to go for I/O. It looks like it's the only complete solution on Linux. The reason I need fast byte-level access to the bitmap displayed on a form (TBitmap) is that I do a lot of batch processing inside an OpenCV library. I feed the library each input and output filename (OCV does its own file I/O), and the pascal batch loop controls the progress bar. But I can't monitor what the output images look like.

The default OCV display window (cv::namedWindow, highgui.hpp) doesn't play nice with GTK when launched from inside a llibrary. It crashes everything. So now the batch loop will interface with the OCV library using a pointer to bytes which will periodically get updated and "Moved" to the display TBitmap.RawImage.Data, and I can monitor the output of the batch processing.

af0815

  • Hero Member
  • *****
  • Posts: 1284
Re: [SOLVED] Is TBGRABitmap.Data always contiguous?
« Reply #9 on: June 16, 2021, 10:47:15 am »
OpenCV library

BTW: Which version of OpenCV you use and what pascal wrapper ?!  The reason is, i want to know a functional version of OpenCV for fpc/Lazarus
regards
Andreas

del

  • Sr. Member
  • ****
  • Posts: 258
Re: [SOLVED] Is TBGRABitmap.Data always contiguous?
« Reply #10 on: June 16, 2021, 01:02:07 pm »
OpenCV library

BTW: Which version of OpenCV you use and what pascal wrapper ?!  The reason is, i want to know a functional version of OpenCV for fpc/Lazarus
I use the modern C++ version of OpenCV (right now 4.5):

https://forum.lazarus.freepascal.org/index.php/topic,54994.0.html

But AFAIK there is no wrapper for C++ OpenCV. Delphi did one for the C OpenCV (deprecated). Only Java and Python have done wrappers for the C++ version. But you don't need a full wrapper if you know enough C++ to stitch together a few OpenCV functions. Here's a short one that crops:

Code: C  [Select][+][-]
  1. int FileCrop(char* iFile, char* oFile, int ulX, int ulY, int width, int height, int useOcl)
  2. {
  3.   if (useOcl)
  4.   {
  5.     SetUpOcl();
  6.     cv::Mat tmpImg = imread(iFile, cv::IMREAD_UNCHANGED);
  7.     cv::UMat imX = tmpImg.getUMat(cv::ACCESS_READ);
  8.     int x1 = Clip<int>(0, imX.cols - 1, ulX + width - 1);
  9.     int y1 = Clip<int>(0, imX.rows - 1, ulY + height - 1);
  10.     cv::Rect cRect(ulX, ulY, 1 + x1 - ulX, 1 + y1 - ulY);
  11.     cv::UMat imY = imX(cRect).clone();
  12.     imwrite(oFile, imY);
  13.   }
  14.   else
  15.   {
  16.     cv::Mat imX = imread(iFile, cv::IMREAD_UNCHANGED);
  17.     int x1 = Clip<int>(0, imX.cols - 1, ulX + width - 1);
  18.     int y1 = Clip<int>(0, imX.rows - 1, ulY + height - 1);
  19.     cv::Rect cRect(ulX, ulY, 1 + x1 - ulX, 1 + y1 - ulY);
  20.     cv::Mat imY = imX(cRect).clone();
  21.     imwrite(oFile, imY);
  22.   }
  23.  
  24.   return 0;
  25. }

The pascal loop that calls it is this:

Code: Pascal  [Select][+][-]
  1. //blah blah blah
  2. libHandle := LoadLibrary('/home/me/PROJECTS/MiscCpp/CommonUtils/lib/release libYourOcvLibrary.so');
  3. pointer(FileCrop) := DynLibs.GetProcedureAddress(libHandle,pchar('FileCrop'));
  4.  
  5. for i := 0 to dirSeq.m_goodFiles - 1 do
  6. begin
  7.   inFile := MFileName.Create(dirSeq.m_filesList[i], false);
  8.   outFile := MFileName.Create(inFile);
  9.   outFile.SetPath(outDir, true);
  10.   FileCrop(pchar(inFile.FullNameA), pchar(outFile.FullNameA), ulX, ulY, cropWidth, cropHeight, ocl);
  11.   inFile.Free;
  12.   outFile.Free;
  13. end;
  14.  
  15. UnloadLibrary(libHandle);

To set up the pascal you need:

Code: Pascal  [Select][+][-]
  1. uses DynLibs;
  2. var
  3.   libHandle: TLibHandle = DynLibs.NilHandle;
  4.  
  5.   FileCrop: procedure(iFile, oFile: pchar; ulX, ulY, width, height, useOcl: integer); cdecl;
  6.  
  7. begin
  8.  // blah blah blah

The C++ header needs extern "C" to keep the function names in the library from being "mangled" or "decorated".

Code: Text  [Select][+][-]
  1. #ifndef yourocvlib_h__
  2. #define yourocvlib_h__
  3.  
  4. #ifdef __cplusplus
  5. extern "C"{
  6. #endif
  7.  
  8. int FileCrop(char* iFile, char* oFile, int ulX, int ulY, int width, int height, int useOcl);
  9.  
  10. #ifdef __cplusplus
  11. }
  12. #endif
  13.  
  14. #endif  // yourocvlib_h__

I can show you a simple Makefile that would build the Linux .so library. To verify that the function name didn't get mangled you can run the Linux command: nm -D libYourOcvLibrary.so | grep Crop
Anyhoo, you can pass pointers to simple data types. I've even flattened a TStringList into a long delimited pChar that gets unpacked in the C++.

« Last Edit: June 16, 2021, 01:04:44 pm by del »

af0815

  • Hero Member
  • *****
  • Posts: 1284
Re: [SOLVED] Is TBGRABitmap.Data always contiguous?
« Reply #11 on: June 16, 2021, 02:00:05 pm »
Thx 4 sharing this interesting information.
regards
Andreas

circular

  • Hero Member
  • *****
  • Posts: 4181
    • Personal webpage
Re: [SOLVED] Is TBGRABitmap.Data always contiguous?
« Reply #12 on: June 19, 2021, 05:33:36 pm »
Just to give some extra info on Data in BGRABitmap.

There are 3 things to consider: the line order, padding and pixel format.

Line order can be bottom to top or top to bottom depending on the system. So it is ok to loop over all pixels to change them as long as it doesn't matter what is the vertical position. Otherwise, you need to use Scanline for each line or to apply the correct delta, either adding or subtracting RowSize bytes or Width for a PBGRAPixel type.

About padding, it can happen with OpenGL bitmaps. The TBGLBitmap can be padded in order to have a size that is compatible with OpenGL. So in this case, RowSize will be a correct delta in bytes, but Width won't be. You need to use AllocatedWidth instead to get the delta in pixels. You could as well loop though all the Data by using AllocatedWidth*Height pixels, though that would be doing unnecessary processing for the padding pixels.

About pixel format, TBGABitmap is always 8-bit BGRA or RGBA. But you can use another format by using another class. Currently those are defined:
- TLinearRGBABitmap contains 32-bit float linear RGBA (16 bytes total per pixel)
- TExpandedBitmap contains word linear RGBA (8 bytes total per pixel)
- TGrayscaleMask contains 1-byte linear gray
- TXYZABitmap contains 32-bit float XYZA (16 bytes total per pixel)
- TWordXYZABitmap contains word XYZA (8 bytes total per pixel)

I guess one could define a 24-bit RGB format. You can look at existing formats to see how it is done to create one if you're interested. If you have any question, feel free to ask.
Conscience is the debugger of the mind

del

  • Sr. Member
  • ****
  • Posts: 258
Re: [SOLVED] Is TBGRABitmap.Data always contiguous?
« Reply #13 on: June 19, 2021, 07:25:58 pm »
Just to give some extra info on Data in BGRABitmap.

There are 3 things to consider: the line order, padding and pixel format.

Line order can be bottom to top or top to bottom depending on the system. So it is ok to loop over all pixels to change them as long as it doesn't matter what is the vertical position. Otherwise, you need to use Scanline for each line or to apply the correct delta, either adding or subtracting RowSize bytes or Width for a PBGRAPixel type.

About padding, it can happen with OpenGL bitmaps. The TBGLBitmap can be padded in order to have a size that is compatible with OpenGL. So in this case, RowSize will be a correct delta in bytes, but Width won't be. You need to use AllocatedWidth instead to get the delta in pixels. You could as well loop though all the Data by using AllocatedWidth*Height pixels, though that would be doing unnecessary processing for the padding pixels.

About pixel format, TBGABitmap is always 8-bit BGRA or RGBA. But you can use another format by using another class. Currently those are defined:
- TLinearRGBABitmap contains 32-bit float linear RGBA (16 bytes total per pixel)
- TExpandedBitmap contains word linear RGBA (8 bytes total per pixel)
- TGrayscaleMask contains 1-byte linear gray
- TXYZABitmap contains 32-bit float XYZA (16 bytes total per pixel)
- TWordXYZABitmap contains word XYZA (8 bytes total per pixel)

I guess one could define a 24-bit RGB format. You can look at existing formats to see how it is done to create one if you're interested. If you have any question, feel free to ask.
Thanks. The more I looked at TBGRABitmap, the more it seemed like you solved several cross-platform problems in one stroke, and added many sophisticated functions. I remember the vertical flip of the scanlines from the CImage class in MFC. I don't remember the reason for it but there was a flag somewhere that indicated if they were flipped so you could unflip them if needed. The padded column width in bytes (stride) I've seen in other places too.

circular

  • Hero Member
  • *****
  • Posts: 4181
    • Personal webpage
Re: [SOLVED] Is TBGRABitmap.Data always contiguous?
« Reply #14 on: June 20, 2021, 12:04:37 am »
Glad I achieved that.  :)

I fixed the line order depending on the OS to make it simpler to render.

Yep also saw the raw stride on other occasions.
Conscience is the debugger of the mind

 

TinyPortal © 2005-2018