Recent

Author Topic: Fastest way for fill pixels of TBitMap from array of Bytes of RGB colors  (Read 8302 times)

yurkad

  • Full Member
  • ***
  • Posts: 173
  • development
I have array of bytes of RGB colors.

Which way is fastest for put it in Graphics.TBitmap?

Thanks :)

flowCRANE

  • Hero Member
  • *****
  • Posts: 956
Provide more information (a more detailed description of the problem and the current code), because we do not know exactly what you want to do.
Lazarus 4.6 with FPC 3.2.2, Windows 11 — all 64-bit

Working solo on a top-down retro-style action/adventure game (pixel art), programming the engine from scratch, using Free Pascal and SDL3.

yurkad

  • Full Member
  • ***
  • Posts: 173
  • development
Ok.

I have array of bytes. Each byte is Red value or Green value or Blue value of TColor.

Code: Pascal  [Select][+][-]
  1. procedure FillBitMap(BitMap: Graphics.TBitMap);
  2.  
  3. Type
  4. TArrOfBytes = array of bytes;
  5.  
  6. var
  7.   PixelsArray      : TArrOfBytes;
  8.   size             : Integer;
  9.   len              : Integer;
  10.  
  11. const
  12.   red=0;green=1;blue=2;
  13.  
  14. begin
  15.  
  16.   len := BitMap.Width * BitMap.Height;
  17.   size := len * 3;
  18.  
  19.   SetLength(PixelsArray, size);
  20.  
  21.   PixelsArray[0 + red] := 100;
  22.   PixelsArray[0 + green] := 28;
  23.   PixelsArray[0 + blue] := 200;
  24.  
  25.   PixelsArray[1 + red] := 40;
  26.   PixelsArray[1 + green] := 18;
  27.   PixelsArray[1 + blue] := 57;
  28.  
  29.   ...
  30.  
  31.   PixelsArray[len+ red] := 59;
  32.   PixelsArray[len + green] := 128;
  33.   PixelsArray[len + blue] := 240;
  34.  
  35. // Total size bytes
  36.  
  37.  
  38. {
  39.   Here I need code for fastest fill the BitMap by
  40.   pixels of PixelsArray.
  41. }
  42.  
  43. end;
  44.  

I tested :

1. Use ScanLine. It's faster for now. And this method little depends on the size of the bitmap.
But it's not cross-platform.

2. GetDataLineStart (Row) of TLazIntfImage. It's cross-platform (I'm not sure), but it's several times slower with the CopyPixels method.
And this method depends too much on the size of the bitmap.

I'm looking for something very fast and cross platform.

Thanks.
 

flowCRANE

  • Hero Member
  • *****
  • Posts: 956
I'm looking for something very fast and cross platform.

The fastest way known for me is ScanLine (which I often use).

Quote
1. Use ScanLine. It's faster for now. And this method little depends on the size of the bitmap.
But it's not cross-platform.

No, ScanLine itself is portable between platforms, but the pixel format between platforms varies. Under Windows, the TBitmap instance created in memory stores 24-bit pixels in BGR order. Under Unixes, in-memory bitmap stores 32-bit pixels in BGRA order. In addition, the pixel format may change eg when a bitmap is loaded from a file or from a stream.

If you care about a portable solution then you need to consider these differences.
Lazarus 4.6 with FPC 3.2.2, Windows 11 — all 64-bit

Working solo on a top-down retro-style action/adventure game (pixel art), programming the engine from scratch, using Free Pascal and SDL3.

yurkad

  • Full Member
  • ***
  • Posts: 173
  • development
@furious programming, it is useful information for me.

Many thanks.

About differences no problem. But I do not know how detect under which platform is running code.

And how about the speeds by using ScanLine and LoadFromStream? Where speed is more?



sstvmaster

  • Sr. Member
  • ****
  • Posts: 306
I have an OT question about it.

Under Windows, the TBitmap instance created in memory stores 24-bit pixels in BGR order. Under Unixes, in-memory bitmap stores 32-bit pixels in BGRA order.

Why the Lazarus Devs can't change this? Or is the 24bit fix for Windows and 32bit for Unixes?
greetings Maik

Windows 10,
- Lazarus 4.4 (stable) + fpc 3.2.2 (stable)
- Lazarus 4.99 (trunk) + fpc 3.3.1 (main/trunk)

yurkad

  • Full Member
  • ***
  • Posts: 173
  • development
I just made code for test LoadFromStream.

Code: Pascal  [Select][+][-]
  1. ...
  2. var
  3.   MemStream        : TMemoryStream;
  4.   PixelsArray      : TArrOfBytes;
  5.   size             : Integer;
  6.   len              : Integer;
  7. begin
  8.   len := BitMap.Width * BitMap.Height;
  9.   size := len * 3;
  10.  
  11.   MemStream := TMemoryStream.Create;
  12.   MemStream.Read(PixelsArray, size);
  13.   MemStream.Seek(0, soBeginning);
  14.   Bitmap.LoadFromStream(MemStream, size);  
  15. end;

In line 14 Error: "Stream read error".
Please, where is my error?
« Last Edit: April 09, 2019, 09:40:01 pm by yurkad »

howardpc

  • Hero Member
  • *****
  • Posts: 4144
A TBitmap is a complex class with a sophisticated ancestry.

You cannot pretend it is merely an array of pixels, even if, deep in its internals it contains such.

yurkad

  • Full Member
  • ***
  • Posts: 173
  • development
For some reason line
Code: Pascal  [Select][+][-]
  1. 12.   MemStream.Read(PixelsArray, size);
is not working correctly.

I did a test and discovered that the content of MemStream is bad after line 12.

jamie

  • Hero Member
  • *****
  • Posts: 7707
I do think that is dynamic "TArrOFBytes" you first need to set the length to it..

Of course I could be wrong as I have been many times in my live!
The only true wisdom is knowing you know nothing

yurkad

  • Full Member
  • ***
  • Posts: 173
  • development
Dear jamie, all of us were wrong many times, too many times!  :D

flowCRANE

  • Hero Member
  • *****
  • Posts: 956
But I do not know how detect under which platform is running code.

Using the compiler directives, for example:

Code: Pascal  [Select][+][-]
  1. {$IFDEF WINDOWS}
  2. // Windows-specific code
  3. {$ENDIF}
  4.  
  5. {$IFDEF UNIX}
  6. // Unix-specific code
  7.  
  8.   {$IFDEF LINUX}
  9.   // Linux-specific code
  10.   {$ENDIF}
  11.  
  12.   {$IFDEF FREEBSD}
  13.   // FreeBSD-specific code
  14.   {$ENDIF}
  15.  
  16.   {..}
  17. {$ENDIF}

Such code construction allows to write the code in such a way that it can be compiled on different platforms, using appropriate elements (eg data types). But a lot of data types are already declared in the right way, so you can use them on different platforms. Many of them are, for example, in the LCLType module, including TRGBTriple and TRGBQuad, which are used in conjunction with ScanLine (although you can declare a similar one yourself).

Quote
And how about the speeds by using ScanLine and LoadFromStream? Where speed is more?

You don't understand, these two concepts are not unambiguous. LoadFromStream is a method that allows you to load bitmap from any stream, and that's all. ScanLine is used to get the pointer to the row of pixels and that's all. You can use ScanLine without LoadFromStream and vice versa.

ScanLine allows you to write effective code, because it allows you to modify image data by directly modifying the components of its pixels.


A simple example of using ScanLine below. This procedure allows you to darken the bitmap according to the given level.

Code: Pascal  [Select][+][-]
  1. type
  2.   // single pixel
  3.   TBitmapPixel = record
  4.     B, G, R {$IFDEF UNIX}, A {$ENDIF}: UInt8;
  5.   end;
  6.  
  7. type
  8.   // row of pixels
  9.   PBitmapLine = ^TBitmapLine;
  10.   TBitmapLine = array [UInt16] of TBitmapPixel;
  11.  
  12. procedure DimBitmap(ABitmap: TBitmap; ADimLevel: UInt8);
  13. var
  14.   // pointer to the pixels line (row)
  15.   Line: PBitmapLine;
  16.   // index of the line (row) and the pixel (column)
  17.   LineIndex, PixelIndex: Integer;
  18. begin
  19.   ADimLevel := 255 - ADimLevel;
  20.  
  21.   // data modification begins (must be called)
  22.   ABitmap.BeginUpdate();
  23.  
  24.   // for each bitmap row
  25.   for LineIndex := 0 to ABitmap.Height - 1 do
  26.   begin
  27.     // get the pointer to the first pixel of the given row
  28.     Line := ABitmap.ScanLine[LineIndex];
  29.  
  30.     // for each pixel of the current row
  31.     for PixelIndex := 0 to ABitmap.Width - 1 do
  32.     begin
  33.       // modify the values of the pixel components, using the pointer to the pixels row
  34.       Line^[PixelIndex].B := Line^[PixelIndex].B * ADimLevel shr 8;
  35.       Line^[PixelIndex].G := Line^[PixelIndex].G * ADimLevel shr 8;
  36.       Line^[PixelIndex].R := Line^[PixelIndex].R * ADimLevel shr 8;
  37.     end;
  38.   end;
  39.  
  40.   // data modification ends (must be called)
  41.   ABitmap.EndUpdate();
  42. end;

Iterating the pixels of each line can also be done in a different way:

Code: Pascal  [Select][+][-]
  1. procedure DimBitmap(ABitmap: TBitmap; ADimLevel: UInt8);
  2. var
  3.   // pointer to the single pixel
  4.   Pixel: PBitmapPixel;
  5.   // index of the line (row) and the pixel (column)
  6.   LineIndex, PixelIndex: Integer;
  7. begin
  8.   ADimLevel := 255 - ADimLevel;
  9.  
  10.   // data modification begins
  11.   ABitmap.BeginUpdate();
  12.  
  13.   // for each bitmap row
  14.   for LineIndex := 0 to ABitmap.Height - 1 do
  15.   begin
  16.     // get the pointer to the first pixel of the given row
  17.     Pixel := ABitmap.ScanLine[LineIndex];
  18.  
  19.     // for each pixel of the current row
  20.     for PixelIndex := 0 to ABitmap.Width - 1 do
  21.     begin
  22.       // modify the values of the pixel components, using the pointer to the current pixel
  23.       Pixel^.B := Pixel^.B * ADimLevel shr 8;
  24.       Pixel^.G := Pixel^.G * ADimLevel shr 8;
  25.       Pixel^.R := Pixel^.R * ADimLevel shr 8;
  26.      
  27.       // move (increment) the pointer to the next pixel in row
  28.       Inc(Pixel);
  29.     end;
  30.   end;
  31.  
  32.   // data modification ends
  33.   ABitmap.EndUpdate();
  34. end;
« Last Edit: April 09, 2019, 11:54:59 pm by furious programming »
Lazarus 4.6 with FPC 3.2.2, Windows 11 — all 64-bit

Working solo on a top-down retro-style action/adventure game (pixel art), programming the engine from scratch, using Free Pascal and SDL3.

lucamar

  • Hero Member
  • *****
  • Posts: 4217
In line 14 Error: "Stream read error".
Please, where is my error?

TBitmap.LoadFromStream expects the stream to contain a valid bitmap structure, exactly as it would be read by TBitmap.LoadFromFile (which in fact uses LoadFromStream to read from a file stream). From its point of view all you're giving it is a random collection of bytes with which, of course, it doesn't know what to do.
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus/FPC 2.0.8/3.0.4 & 2.0.12/3.2.0 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

VTwin

  • Hero Member
  • *****
  • Posts: 1227
  • Former Turbo Pascal 3 user
I am not clear on the details of exactly what you need to do, but I bring to your attention, if you are not already familiar, with BGRABitmap which has many fast cross-platform routines for loading bitmaps.
“Talk is cheap. Show me the code.” -Linus Torvalds

Free Pascal Compiler 3.2.2
macOS 15.3.2: Lazarus 3.8 (64 bit Cocoa M1)
Ubuntu 18.04.3: Lazarus 3.8 (64 bit on VBox)
Windows 7 Pro SP1: Lazarus 3.8 (64 bit on VBox)

yurkad

  • Full Member
  • ***
  • Posts: 173
  • development
@furious programming, thanks by good example about scanline and help for detect platform.

I used something similar, but after
Code: Pascal  [Select][+][-]
  1.   // for each bitmap row
  2.   for LineIndex := 0 to ABitmap.Height - 1 do
  3.   begin
instead
Code: Pascal  [Select][+][-]
  1.     // get the pointer to the first pixel of the given row
  2.     Line := ABitmap.ScanLine[LineIndex];
  3.  
  4.     // for each pixel of the current row
  5.     for PixelIndex := 0 to ABitmap.Width - 1 do
  6.     begin
  7.       // modify the values of the pixel components, using the pointer to the pixels row
  8.       Line^[PixelIndex].B := Line^[PixelIndex].B * ADimLevel shr 8;
  9.       Line^[PixelIndex].G := Line^[PixelIndex].G * ADimLevel shr 8;
  10.       Line^[PixelIndex].R := Line^[PixelIndex].R * ADimLevel shr 8;
  11.     end;

 I put following cod:
Code: Pascal  [Select][+][-]
  1. {Here
  2. lScanlineDest   : Pointer;
  3. PixelsArray     : TArrOfBytes; (array of byte)}
  4. lScanlineDest := bm.scanline[yy];
  5. move(PixelsArray[0], PByte(lScanlineDest^), ABitmap.Width * 3); // Is Working

Using of move is faster, but it is have not a sense here.

The cause is that by using any standard method of Lazarus or Delphi for change pixels we cannot obtain good speed by following problem:
When we change some pixels each standard method begin
make struct of TBitMap. Aproximate 95% of time takes work for change palette.
1. Color of each changed pixel is necessary compare with each color of palette for detect if it is new color or already exists. If changed color is new then standard method
2. add it to palette,
3. increase used memory for new color,
4. assign new number for new color,
5. and this new color fixes to new pixel instead color of this pixel.

It is slowly and coder cannot avoit it.

Does exists two better ways :

1. Make struct of TBitMap and use Bitmap.LoadFromStream.
  It is difficult but is fastest.

2. Generate new palette and use SetPalette method. After this
can be change any pixel, but instead color use number of this color from generate palette. It would be much more faster than any standard method, but regrettably I did not find methods for perform this.

@lucamar and @howardpc, many thanks by comment. You posts helped me change point of view.
« Last Edit: April 10, 2019, 09:29:23 pm by yurkad »

 

TinyPortal © 2005-2018