Recent

Author Topic: [SOLVED] Help in Using ScanLine  (Read 7154 times)

Xor-el

  • Sr. Member
  • ****
  • Posts: 404
[SOLVED] Help in Using ScanLine
« on: October 23, 2018, 05:26:53 pm »
Hi, I am looking to optimize a particular routine with ScanLine.
I have read several documentation but can't seem to wrap my head round it.
Please How do I convert this method to use TBitMap.ScanLine.

Code: Pascal  [Select][+][-]
  1. function TQrCode.ToBmpImage(AScale, ABorder: Int32): TBitmap;
  2. var
  3.   LColumn, LRow: Int32;
  4.   LDoColor: Boolean;
  5.   LBrushColor: TColor;
  6. begin
  7.  
  8.   Result := TBitmap.Create;
  9.  
  10.   Result.SetSize((FSize + (ABorder * 2)) * AScale, (FSize + (ABorder * 2))
  11.     * AScale);
  12.   try
  13. {$IFDEF FPC}
  14.     // update locking for speedup. only available in FPC
  15.     Result.BeginUpdate(True);
  16. {$ENDIF FPC}
  17.     for LColumn := 0 to System.Pred(Result.Height) do
  18.     begin
  19.       for LRow := 0 to System.Pred(Result.Width) do
  20.       begin
  21.         if Math.RandomRange(0, 1) = 0 then
  22.         LDoColor := true
  23.         else
  24.         LDoColor := false;
  25.         if LDoColor then
  26.         begin
  27.           LBrushColor := clBlack;
  28.         end
  29.         else
  30.         begin
  31.           LBrushColor := clWhite;
  32.         end;
  33.         Result.Canvas.Pixels[LRow, LColumn] := LBrushColor;
  34.       end;
  35.     end;
  36.   finally
  37. {$IFDEF FPC}
  38.     // update locking for speedup. only available in FPC
  39.     Result.EndUpdate(false);
  40. {$ENDIF FPC}
  41.   end;
  42. end;
  43.  

Thanks.
« Last Edit: October 25, 2018, 09:51:41 am by Xor-el »

jamie

  • Hero Member
  • *****
  • Posts: 6090
Re: Help in Using ScanLine
« Reply #1 on: October 24, 2018, 01:12:58 am »
You are missing a few details you must do prior to this if you expect to do direct memory accessing..

First of all you need to define the Pixel Format to use prior to setting the size but after the creation...

Pixel format defines the amount of space per Pixel in memory of the image.

The Laz boys would prefer  you to use "GetStartLine(?)" instead. That will return a byte Pointer to the memory.

Come back for more details if this interest you.

The only true wisdom is knowing you know nothing

Xor-el

  • Sr. Member
  • ****
  • Posts: 404
Re: Help in Using ScanLine
« Reply #2 on: October 24, 2018, 01:27:01 am »
You are missing a few details you must do prior to this if you expect to do direct memory accessing..

First of all you need to define the Pixel Format to use prior to setting the size but after the creation...

Pixel format defines the amount of space per Pixel in memory of the image.

The Laz boys would prefer  you to use "GetStartLine(?)" instead. That will return a byte Pointer to the memory.

Come back for more details if this interest you.

Thanks Jamie for replying.
Yes am interested.
I will use pf32bit pixel format.
« Last Edit: October 24, 2018, 01:32:41 am by Xor-el »

jamie

  • Hero Member
  • *****
  • Posts: 6090
Re: Help in Using ScanLine
« Reply #3 on: October 24, 2018, 01:45:43 am »

// Before setting the image size
Result.PixelFormat := pf32bit;

//Set image size now;

Make types

Type
   TMyPixel = Array[0..integer] of Dword;
   PMyPixel = ^TMyPixel;

 // At the start of each X scan in loop
 PMyPixel :=PMyPixel( Result.GetStartLine(LineNumber));

 // and in the loop;;

  PMyPixel[ X ]^ := SomeRandomValue;

I think you get the idea. This gives you a pointer to the start if each line in memory.


 
The only true wisdom is knowing you know nothing

jamie

  • Hero Member
  • *****
  • Posts: 6090
Re: Help in Using ScanLine
« Reply #4 on: October 24, 2018, 02:14:20 am »
Here is an example you can use...
Code: Pascal  [Select][+][-]
  1. Function  GetRandom32BitMap(aX,aY:Integer):TBitMap;
  2.  Type
  3.     TPixs = Array[0..32768] of Dword;
  4.     PPixs = ^TPixs;
  5.  Var
  6.    LinePixs:PPixs;
  7.    X,Y:Integer;
  8. Begin
  9.   Result := Tbitmap.Create;
  10.   Result.Pixelformat := pf32bit;
  11.   Result.SetSize(aX,aY);
  12.   For Y := 0 to aY-1 do
  13.    Begin
  14.       LinePixs := Result.ScanLine[Y];
  15.     For X := 0 To aX-1 do
  16.       LinePixs^[X]:= RanDom(256*256*256);
  17.    end;
  18. end;
  19.  
  20. procedure TForm1.Button1Click(Sender: TObject);
  21. Var
  22.   B:TBitmap;
  23. begin
  24.   B := GetRandom32BitMap(100,100);
  25.   Canvas.Draw(0,0,B);
  26.   B.Free;
  27. end;                            
  28.  
The only true wisdom is knowing you know nothing

Xor-el

  • Sr. Member
  • ****
  • Posts: 404
Re: Help in Using ScanLine
« Reply #5 on: October 24, 2018, 04:30:38 am »
Here is an example you can use...
Code: Pascal  [Select][+][-]
  1. Function  GetRandom32BitMap(aX,aY:Integer):TBitMap;
  2.  Type
  3.     TPixs = Array[0..32768] of Dword;
  4.     PPixs = ^TPixs;
  5.  Var
  6.    LinePixs:PPixs;
  7.    X,Y:Integer;
  8. Begin
  9.   Result := Tbitmap.Create;
  10.   Result.Pixelformat := pf32bit;
  11.   Result.SetSize(aX,aY);
  12.   For Y := 0 to aY-1 do
  13.    Begin
  14.       LinePixs := Result.ScanLine[Y];
  15.     For X := 0 To aX-1 do
  16.       LinePixs^[X]:= RanDom(256*256*256);
  17.    end;
  18. end;
  19.  
  20. procedure TForm1.Button1Click(Sender: TObject);
  21. Var
  22.   B:TBitmap;
  23. begin
  24.   B := GetRandom32BitMap(100,100);
  25.   Canvas.Draw(0,0,B);
  26.   B.Free;
  27. end;                            
  28.  

Thanks a lot for your example jamie, it works very well with just one minor issue, if I try to assign the created bmp to a png object and save, I get a blank png image but the bmp image saves properly.

Here is my saving code

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. Var
  3.   B:TBitmap;
  4.   p: TPortableNetworkGraphic;
  5. begin
  6.   p := TPortableNetworkGraphic.Create();
  7.   B := GetRandom32BitMap(100,100);
  8.   Canvas.Draw(0,0,B);
  9.  B.SaveToFile('B.bmp');
  10.  p.Assign(b);
  11.  p.SaveToFile('P.png');
  12.   B.Free;
  13.   P.Free;
  14. end;
  15.  

Any Idea what might be the issue?

Thanks once again.

jamie

  • Hero Member
  • *****
  • Posts: 6090
Re: Help in Using ScanLine
« Reply #6 on: October 24, 2018, 03:33:16 pm »
Hard to say really, I never used that Png component before.

It maybe possible that you didn't set the size of the png to that of the bitmap prior to assigning?
also the png component may have a pixel format that may also need setting before the Assignment

 There is another possibility and it comes from how the bitmap works:

   After setting the size of the bitmap in the function do this:
      Result.Canvas.Changed;
   
  That creates a windows Handled BMP object instead of the LCL direct memory style which they consider faster but
I have my doubts about that these days :o
The only true wisdom is knowing you know nothing

Handoko

  • Hero Member
  • *****
  • Posts: 5129
  • My goal: build my own game engine using Lazarus
Re: Help in Using ScanLine
« Reply #7 on: October 24, 2018, 04:13:06 pm »
I remember on Graphics Contest 2017, I used Lazarus internal image loading/saving feature but it didn't work correctly. So I used fcl-image as the alternative. If I remember correctly, the main issue was the ability to process tiff images. I won't said it is a bug, I think it's just the limitation of Lazarus internal image loading/saving ability.

http://forum.lazarus.freepascal.org/index.php/topic,35313.msg258981.html#msg258981

Not sure if it helps, but you can try fcl-image. On my tests, it works better than Lazarus internal image processing:
http://wiki.lazarus.freepascal.org/fcl-image

Xor-el

  • Sr. Member
  • ****
  • Posts: 404
Re: Help in Using ScanLine
« Reply #8 on: October 24, 2018, 05:35:06 pm »

It maybe possible that you didn't set the size of the png to that of the bitmap prior to assigning?
also the png component may have a pixel format that may also need setting before the Assignment
I did try both of them yet no progress.

There is another possibility and it comes from how the bitmap works:
   After setting the size of the bitmap in the function do this:
      Result.Canvas.Changed;
   
  That creates a windows Handled BMP object instead of the LCL direct memory style which they consider faster but
I have my doubts about that these days :o

Same Issue as above.

I did notice one thing though, If I draw the Png Object that was assigned from the Bitmap via Canvas on a form, it works beautifully, the issue seems to come from saving to file.

Other formats (Jpeg, Bmp), works fine when saving to file after assigning the bitmap to their respective internal formats (TJPEGImage, TBitMap), the only problem is PNG (TPortableNetworkGraphic).
One funny thing is that it works beautifully in Delphi.
« Last Edit: October 24, 2018, 05:39:39 pm by Xor-el »

Xor-el

  • Sr. Member
  • ****
  • Posts: 404
Re: Help in Using ScanLine
« Reply #9 on: October 24, 2018, 05:36:43 pm »
I remember on Graphics Contest 2017, I used Lazarus internal image loading/saving feature but it didn't work correctly. So I used fcl-image as the alternative. If I remember correctly, the main issue was the ability to process tiff images. I won't said it is a bug, I think it's just the limitation of Lazarus internal image loading/saving ability.

http://forum.lazarus.freepascal.org/index.php/topic,35313.msg258981.html#msg258981

Not sure if it helps, but you can try fcl-image. On my tests, it works better than Lazarus internal image processing:
http://wiki.lazarus.freepascal.org/fcl-image

Thanks for replying but unfortunately, am stuck with the LCL Classes.

Xor-el

  • Sr. Member
  • ****
  • Posts: 404
Re: Help in Using ScanLine
« Reply #10 on: October 24, 2018, 10:44:44 pm »
changing the pixel format to pf24bit crashes the program too.
Code: Pascal  [Select][+][-]
  1. Result.Pixelformat := pf24bit;
  2.  

jamie

  • Hero Member
  • *****
  • Posts: 6090
Re: Help in Using ScanLine
« Reply #11 on: October 25, 2018, 12:19:22 am »
you can't change the pixel format without  changing the array type.

24 bit takes 3 bytes per pixel and the example I gave you is using a DWORD for simplicity which is 32 bits.

 if you were to try a 24 bit you would need to change line array type to something that is equal in 3 bytes because
the way it is now the math to index the memory image is doing so in steps of 4 bytes not 3 in which this would lead to
a memory overrun.

  Getting back to your saving issues with png, I looked a the code and it does not appear that control is overriding the
SaveToFile so in assents it looks like it maybe doing nothing or just writing a BMP instead..

 Try using a TPicture object instead and load and read from that so see what happens, that class does override.

The only true wisdom is knowing you know nothing

Xor-el

  • Sr. Member
  • ****
  • Posts: 404
Re: Help in Using ScanLine
« Reply #12 on: October 25, 2018, 01:04:19 am »
you can't change the pixel format without  changing the array type.

24 bit takes 3 bytes per pixel and the example I gave you is using a DWORD for simplicity which is 32 bits.

 if you were to try a 24 bit you would need to change line array type to something that is equal in 3 bytes because
the way it is now the math to index the memory image is doing so in steps of 4 bytes not 3 in which this would lead to
a memory overrun.

  Getting back to your saving issues with png, I looked a the code and it does not appear that control is overriding the
SaveToFile so in assents it looks like it maybe doing nothing or just writing a BMP instead..

 Try using a TPicture object instead and load and read from that so see what happens, that class does override.

Hi jamie,
Thanks for your patience and time.
also thank you for explaining the concept behind the pixel format.
tried your recent suggestion yet unfortunately no progress.

I have prepared a demo to show my issue.

in this demo, Png, Jpg and bmp are successfully painted on the canvas but when trying to write them to file, png seems to save just a white background image, others succeed. I think it has something to do with png not preserving the alpha channel but it's just a wild guess.

Attached below is the demo.

Thanks once again.

« Last Edit: October 25, 2018, 01:10:48 am by Xor-el »

jamie

  • Hero Member
  • *****
  • Posts: 6090
Re: Help in Using ScanLine
« Reply #13 on: October 25, 2018, 02:00:30 am »
Ok, I got it to work, it seems it does not like 32 bit images, must be something wrong with it...

so I use the existing random function to define the bmp but I then created another bmp2.pixelformat := pf24bit;

then use the Draw to draw the 32 bit one on top of the 24 bit one.. from there you can assign the 24 bit image to the
png and it seems to work but, I did use a Tpicture to do this..

Picture.png := png;
picture.SaveTOFile(xxxx.png);

 basically the png just does not like 32 bit images I guess or at least not the ones the bmp generates.

P.S.
 Just tried it with using the png only after the assignment and seems to work there also as long as the bmp being assigned is
a 24 bit..


« Last Edit: October 25, 2018, 02:04:21 am by jamie »
The only true wisdom is knowing you know nothing

furious programming

  • Hero Member
  • *****
  • Posts: 852
Re: Help in Using ScanLine
« Reply #14 on: October 25, 2018, 04:26:35 am »
I'm using a lot of procedures for processing bitmaps and PNG that uses ScanLine. But I have declared custom types to represents BGR pixels. I'm using my code for Windows only and it works excellent.

Additional types:

Code: Pascal  [Select][+][-]
  1. type
  2.   TRGBTriple = packed record
  3.     B, G, R: UInt8;
  4.   end;
  5.  
  6. type
  7.   PRGBTripleArr = ^TRGBTripleArr;
  8.   TRGBTripleArr = packed array [0 .. MaxInt div SizeOf(TRGBTriple) - 1] of TRGBTriple;

Sample procedure to process 24-bit bitmap below. This procedure is used to dim (darken) the bitmap:

Code: Pascal  [Select][+][-]
  1. procedure DimBitmap(ABitmap: TBitmap; ALevel: UInt8);
  2. var
  3.   Line: PRGBTripleArr;
  4.   LineIndex, PixelIndex: Integer;
  5. begin
  6.   ABitmap.BeginUpdate();
  7.   ALevel := 255 - ALevel;
  8.  
  9.   for LineIndex := 0 to ABitmap.Height - 1 do
  10.   begin
  11.     Line := ABitmap.ScanLine[LineIndex];
  12.  
  13.     for PixelIndex := 0 to ABitmap.Width - 1 do
  14.       with Line^[PixelIndex] do
  15.       begin
  16.         B := B * ALevel shr 8;
  17.         G := G * ALevel shr 8;
  18.         R := R * ALevel shr 8;
  19.       end;
  20.   end;
  21.  
  22.   ABitmap.EndUpdate();
  23. end;

I'm using this procedure in my Deep Platformer semi-game, witch is not public yet. But when I finish this project, I will publish its sources.

Second example is a procedure, that rotates the source PNG:

Code: Pascal  [Select][+][-]
  1. procedure RotatePNGCounterClockwise(AGraphic: TPortableNetworkGraphic);
  2. var
  3.   Source: TPortableNetworkGraphic;
  4.   SourceLine, GraphicLine: PRGBTripleArr;
  5.   LineIndex, PixelIndex: Integer;
  6. begin
  7.   Source := TPortableNetworkGraphic.Create();
  8.   try
  9.     Source.Assign(AGraphic);
  10.     Source.BeginUpdate();
  11.  
  12.     AGraphic.SetSize(Source.Height, Source.Width);
  13.     AGraphic.BeginUpdate();
  14.  
  15.     for LineIndex := 0 to Source.Height - 1 do
  16.     begin
  17.       SourceLine := Source.ScanLine[LineIndex];
  18.  
  19.       for PixelIndex := 0 to Source.Width - 1 do
  20.       begin
  21.         GraphicLine := AGraphic.ScanLine[AGraphic.Height - PixelIndex - 1];
  22.         GraphicLine^[LineIndex] := SourceLine^[PixelIndex];
  23.       end;
  24.     end;
  25.   finally
  26.     AGraphic.EndUpdate();
  27.  
  28.     Source.EndUpdate();
  29.     Source.Free();
  30.   end;
  31. end;

I hope these codes are helpful.
« Last Edit: October 25, 2018, 06:53:41 pm by furious programming »
Lazarus 3.2 with FPC 3.2.2, Windows 10 — all 64-bit

Working solo on an acrade, action/adventure game in retro style (pixelart), programming the engine and shell from scratch, using Free Pascal and SDL. Release planned in 2026.

 

TinyPortal © 2005-2018