5

I am trying to access the scanline of a Bitmap according to an article on Embarcadero. Using scanlines like

for y := 0 to n do
begin
   line := bitmap.scanline [y];
   for x := 0 to n do line [x] := value;

I have implemented before. I noticed that accessing a scanline takes relatively much time and the article mentioned above offers a solution to that. I am not able to implement it correctly. My code is:

unit SCTester;

interface

uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
     ExtCtrls;

type
   TRGBQuad = packed record
      b: uInt8;
      g: uInt8;
      r: uInt8;
      alpha: uInt8;
   end; // Record: TQuad //

// Override the definitions in Graphics.pas
   TRGBQuadArray = packed array [0..MaxInt div SizeOf (TRGBQuad) - 1] of TRGBQuad;
   PRGBQuadArray = ^TRGBQuadArray;

  TForm1 = class(TForm)
    Image: TImage;
    procedure ImageDblClick(Sender: TObject);
  end;

var Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.ImageDblClick(Sender: TObject);
var Bitmap: TBitmap;
    q: TRGBQuad;
    x, y: NativeInt;
    FirstLine: PRGBQuadArray;
    idx: NativeInt;
    LineLength: NativeInt;
begin
   q.r := 0; q.g := 0;
   Bitmap := TBitmap.Create;
   Bitmap.Height := Image.Height;
   Bitmap.Width  := Image.Width;
   Bitmap.PixelFormat := pf32Bit;
   FirstLine := Bitmap.ScanLine [0];
   LineLength := (NativeInt (Bitmap.Scanline [1]) - NativeInt (FirstLine)) div SizeOf (TRGBQuad);
   try
      for y := Bitmap.Height - 1 downto 0 do
      begin
         for x := 0 to Bitmap.Width - 1 do
         begin
            q.b := (x xor y) mod 255;
            idx := y * LineLength + x;
            FirstLine [idx] := q;
         end; // for
      end; // for
      Image.Picture.Assign (Bitmap);
   finally
       Bitmap.Free;
   end; // try..finally
end;

end.

And I always get an illegal access when y=1 and x=0. LineLength is negative (the width of the bitmap), but that might be expected. What am I doing wrong?

EDIT: The code above is changed to reflect the remarks processed till so far.

Arnold
  • 4,578
  • 6
  • 52
  • 91
  • 2
    `idx` should be declared as `NativeInt`, so your code could be used in x64 as well. `LineLength` must not be negative (hence the illegal access). My conclusion is that you are running this code in 64 bit mode. – LU RD May 01 '12 at 16:45
  • @LURD, My thoughts exactly - and every LongInt(...) should be replaced with NativeUInt(...) – kobik May 01 '12 at 16:48
  • @LURD LineLength can be negative (and usually is negative), that is not a problem that cause AV. – kludg May 01 '12 at 17:04
  • I tested the code on Delphi XE and it works; yes, `LineLength` is negative at that is OK. – kludg May 01 '12 at 17:09
  • @Serg, I stand corrected, withdrawing my answer. idx should be declared as `NativeInt` though. – LU RD May 01 '12 at 17:38
  • 3
    @LURD Sure the code is incorrect in 64-bit mode due to pointers casted to longints – kludg May 01 '12 at 17:42
  • @Serg, exactly this code worked on your machine? I run Delphi-XE on windows-7 64 bit. In article http://www.davdata.nl/math/drawing1.html is explained why LineLength usually is negative. – Arnold May 01 '12 at 17:48
  • 1
    @Arnold yes, i have Delphi XE on windows7 64 bit, your code works and I see no other problem in your code except casting pointers to longints in Delphi XE2 64-bit mode. – kludg May 01 '12 at 17:59
  • @Serg, it definitely does not work on my machine. Errors may vary though, now it is an illegal access. I tried to change the project options: set the record field alignment to double word, but that does not help. – Arnold May 01 '12 at 18:12
  • 2
    Change `LineLength := (Longint (Bitmap.Scanline [1]) - Longint (FirstLine))` to `LineLength := (NativeInt (Bitmap.Scanline [1]) - NativeInt (FirstLine))` and all `LongInt` declarations to `NativeInt`. – LU RD May 01 '12 at 18:24
  • @LU RD, thanks for the suggestion. Still a Range check error. Could it have something to do with the fact that TRGBArray is declared from [0..big number] and negative indices are used? When I add 400 (bitmap width) to idx it gives an Range check error for y=2, x=0, adding 800 gives Illegal access at y=0 and x=304. Bitmap.Height = 500. – Arnold May 01 '12 at 19:20
  • @LU RD (and others), Thanks all of you for your efforts, but I don't seem to get it. My apologies for asking more of your time. I have created a small stand-alone program with all your suggestions up to date, see the edited code. I still get the error. If it runs fine on your machines, is there maybe a compiler option that could cause this? – Arnold May 01 '12 at 19:52
  • @Arnold - I really did not look at the code when I commented. When I deleted the comment it was too late.. Sorry.. Take a look at the comment/answer I posted to see if it works as you'd like. – Sertac Akyuz May 01 '12 at 20:09
  • @Sertac, Ah, I thought I'd seen sometrhing passing by :-) Could you repost it please? – Arnold May 01 '12 at 20:14
  • @Arnold - What I'm suggesting now is in the answer. The comment I deleted was wrong.. – Sertac Akyuz May 01 '12 at 20:17
  • @Sertac, I missed that in the stress :-) It is the correct answer. I have marked it. – Arnold May 01 '12 at 20:23

2 Answers2

5

To not to access any negative index, I would do

procedure TForm1.Button1Click(Sender: TObject);
var Bitmap: TBitmap;
    q: TRGBQuad;
    x, y: LongInt;
    line{, FirstLine}: PRGBQuadArray;
    idx: NativeInt;
    LastLine: PRGBQuadArray;
    LineLength: NativeInt;
begin
   q.r := 0; q.g := 0;
   Bitmap := TBitmap.Create;
   Bitmap.Height := Image.Height;
   Bitmap.Width  := Image.Width;
   Bitmap.PixelFormat := pf32Bit;

   LastLine := Bitmap.ScanLine[Bitmap.Height - 1];
   LineLength := (NativeInt(Bitmap.Scanline[Bitmap.Height - 2]) - NativeInt(Lastline)) div SizeOf(TRGBQuad);
   try
      for y := 0 to Bitmap.Height - 1 do
      begin
         for x := 0 to Bitmap.Width - 1 do
         begin
            q.b := (x xor y) mod 255;
            idx := y * LineLength + x;
            LastLine [idx] := q;
         end; // for
      end; // for
      Image.Picture.Assign (Bitmap);
   finally
       Bitmap.Free;
   end; // try..finally
end;
Sertac Akyuz
  • 54,131
  • 4
  • 102
  • 169
  • It Works! Thank you so much! This is indeed a good way to avoid the negative numbers. This will help me to speed my bitmaps even more. – Arnold May 01 '12 at 20:22
  • @Arnold - You're welcome!.. Don't forget to verify that you have a bottom-up bitmap. – Sertac Akyuz May 01 '12 at 20:24
  • 1
    Exactly! What I read in the articles about scanlines is that scanlines are 'usually' ordered from top to bottom, hence generating the negative line lengths. One should be aware that in order to have this code run correctly in all cases one has to code for the positive and negative line lengths explicitly. Edit: I should refer to bottom-up and top-down ordered bitmaps in order to avoid the confusing terminology of negative or positive line lengths. – Arnold May 01 '12 at 20:28
  • Just tested the code. The speed up is incredible. Thanks to all for helping me out. – Arnold May 01 '12 at 20:55
  • 1
    It does not explain what was wrong in the original OP code - compiler settings, 64-bit mode or else. OP did not said even whether he compiles in 32- or 64-bit mode - always dislike questions like that. – kludg May 02 '12 at 02:42
  • 1
    @Serg - By the time I was involved OP was getting a range check error due to using negative index on a 'TRGBQuadArray' ([11th comment](http://stackoverflow.com/questions/10400819/how-to-implement-the-scanline-access-of-tbitmap-correctly/10403621#comment13418717_10400819) on the question as of now). Other possible errors were probably already sorted out. – Sertac Akyuz May 02 '12 at 08:08
  • @Serg, I am sorry for the confusion. I use delphi XE and not XE2 as I erroneously tagged. I did say somewhere in the comments that I used Delphi-XE on win-7-64, that was easy to overlook. My apologies. I got two errors: 'Range check error' and 'illegal access' and never understood when I got the one or the other. – Arnold May 02 '12 at 17:54
1

LineLength is negative for a lot of bitmaps, because they frequently use bottom-up method to store lines. MSDN: BITMAPINFOHEADER. So this solution should be modified for such case.

MBo
  • 77,366
  • 5
  • 53
  • 86
  • I understand what you mean, but I find 'LineLength is negative' a bit confusing. I'd say, in a bottom-up bitmap the first scanline is the last one in the memory layout. – Sertac Akyuz May 01 '12 at 19:31