1

Hello im using this code to get the screen on a canvas

procedure GetScreen(var vBitmap : TBitmap);
var vDC     : hdc;
    vCanvas : TCanvas;
begin
   vDC            := GetDC(0);
   vCanvas := TCanvas.Create;
   vCanvas.Handle := vDC;

   vBitmap.Canvas.CopyRect(Rect(0, 0, Screen.Width, Screen.Height),
                        vCanvas,
                        Rect(0, 0, Screen.Width, Screen.Height));

   vCanvas.Free;
   ReleaseDC(0, vDC);

end;

which takes about 20 ms to execute, having that i run in each pixel and compare with a given color using this function

function CompareColor(Color1, Color2 : TRGBTriple): Double;
var vR, vG, vB : Byte;
begin
   vR := abs(Color1.rgbtRed - Color2.rgbtRed);
   vG := abs(Color1.rgbtGreen - Color2.rgbtGreen);
   vB := abs(Color1.rgbtBlue - Color2.rgbtBlue);

   Result := (((vR + vG + vB) / 3) / 255);
end;

but since i need to convert each TColor to a TRGBTriple

vCorCmp.rgbtRed   := GetRValue(vBitmap.Canvas.Pixels[nX, nY]);
vCorCmp.rgbtGreen := GetGValue(vBitmap.Canvas.Pixels[nX, nY]);
vCorCmp.rgbtBlue  := GetBValue(vBitmap.Canvas.Pixels[nX, nY]);

i lose more than a second to convert the entire TCanvas, my question is, how can i get an TRGBTriple array of the screen instead of a TColor array?

  • Aiside: why do you pass the bitmap as a `var` param? And why pass it at all. Pass its canvas. As for question, if you care about perf, use ScanLine. Pixel[] is slow. Especially if you call it over and again with the same args. Three times perf gain right there. Even better avoid bitmap completely and pull out the pixel data to a memory buffer. Can't beat that. – David Heffernan Aug 02 '14 at 19:41
  • I'm also curious about what CompareColor is attempting to achieve and why you divide twice instead of once. 255*3=765 – David Heffernan Aug 02 '14 at 20:08
  • sorry, i tried to simplify the code, i dont even use a parameter in the function, i just didnt want to misslead the question, i need to run in each pixel because its a flood fill, i just need it to be an array of TRGBTriple so i dont lose time converting it all, oh, i will use the floodfill to get a polygon of all diffferent colors in the screen so in one way or another i need to run through all pixels. the compare function is just a sketch, my problme for now is the converting part – user3902689 Aug 02 '14 at 20:10
  • So use scanline, or get all the pixel values into a single memory. – David Heffernan Aug 02 '14 at 20:12
  • so, if i declare vArray : array of array of ^TRGBTriple set it's size to Screen.height and run a scanline in each row, ill get the array i want? – user3902689 Aug 02 '14 at 20:26
  • Why do you need to make a copy? Scanline gives you pointer to the raw data. – David Heffernan Aug 02 '14 at 20:38
  • what do you mean with a copy? – user3902689 Aug 02 '14 at 20:40
  • Your array of array of triple is a copy. Why make a copy? – David Heffernan Aug 02 '14 at 20:42
  • hmmm, i think i got it wrong, scanline points to the first element of the row right? so i only need an array of TRGBTriple and ill get what i want, then i just make a loop and add the index to pointer and i will have access to the color of the pixel? – user3902689 Aug 02 '14 at 20:47
  • Yes indeed. That's how to do it. Orders of magnitude faster than your current approach. – David Heffernan Aug 02 '14 at 20:48
  • ok, thank you so much, will give it a try, should i answer my own question? :S – user3902689 Aug 02 '14 at 20:50
  • If you think you can do it well. There are already a lot of good scanline answers here. – David Heffernan Aug 02 '14 at 20:51
  • hmmm, i was doing some tests, i was able to get all lines first element in the array, but i cant retrieve others elements, i mean var vRGB : array of ^TRGBTriple; for iY := 0 to Screen.Height - 1 do begin vRGB[iY] := vImg.ScanLine[iY]; end; so, this vRGB[0].rgbtRed = GetRValue(vImg.Canvas.Pixels[0, 0]; and vRGB[1].rgbtRed = GetRValue(vImg.Canvas.Pixels[0, 1]; but how do i get "X" value? the compiler just say "Operator is not applicable to this type of operand." if i try vRGB[0] + 100; – user3902689 Aug 02 '14 at 22:14
  • There's no dynamic arrays here. Find one of the many examples of using scanline. – David Heffernan Aug 03 '14 at 02:25
  • @user3902689, what is your aim ? Maybe we can find an optimal solution. – TLama Aug 03 '14 at 04:49

2 Answers2

3

The TBitmap.Canvas.Pixels[] property is inherently slow. For fast access, you need to use the TBitmap.ScanLines property instead:

type
  TRGBTripleArray = array[0..32767] of TRGBTriple;
  PRGBTripleArray = ^TRGBTripleArray;
var
  Pixels: PRGBTripleArray;
...
vBitmap.PixelFormat := pf24bit;
vBitmap.Canvas.CopyRect(Rect(0, 0, Screen.Width, Screen.Height),
                        vCanvas,
                        Rect(0, 0, Screen.Width, Screen.Height));
...
For nY := 0 to vBitmap.Height-1 do
begin
  pixels := PRGBTripleArray(vBitmap.Scanline[nY]);
  For nX := 0 to vBitmap.Width-1 do
  begin
    vCorCmp.rgbtRed   := pixels^[nX].rgbtRed;
    vCorCmp.rgbtGreen := pixels^[nX].rgbtGreen;
    vCorCmp.rgbtBlue  := pixels^[nX].rgbtBlue;
    ...
  end;
end;

Or, if you are using Delphi 2009 or later:

{$POINTERMATH ON}

var
  Pixels: PRGBTriple;
...
vBitmap.PixelFormat := pf24bit;
vBitmap.Canvas.CopyRect(Rect(0, 0, Screen.Width, Screen.Height),
                        vCanvas,
                        Rect(0, 0, Screen.Width, Screen.Height));
...
For nY := 0 to vBitmap.Height-1 do
begin
  pixels := PRGBTriple(vBitmap.Scanline[nY]);
  For nX := 0 to vBitmap.Width-1 do
  begin
    vCorCmp.rgbtRed   := pixels[nX].rgbtRed;
    vCorCmp.rgbtGreen := pixels[nX].rgbtGreen;
    vCorCmp.rgbtBlue  := pixels[nX].rgbtBlue;
    ...
  end;
end;

See this for more details:

How to use ScanLine property for 24-bit bitmaps?

Community
  • 1
  • 1
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • yes, this is it, i just modified Pixels: PRGBTripleArray; to Pixels: array of PRGBTripleArray; and ran a scanline trhough each line because since im using it for a floodfill i avoid running a scanline more than once in the same row, tank you and thank David Heffernan – user3902689 Aug 03 '14 at 20:57
  • Declaring `pixels` to be a dynamic array is the wrong thing do it. It needs to be a raw pointer. The `array[]` syntax I showed was intentional, as it is how to iterate through the scanline data in older versions of Delphi that do not support `{$POINTERMATH ON}` (I have added a POINTERMATH example). – Remy Lebeau Aug 04 '14 at 00:35
1

Rather than using a TBitmap use a TBitmap32 from Graphics32 (Free from http://graphics32.org). I have used it in several projects for prototyping and found that he performance is consistently several times faster than regular TBitmap even if you use the .pixels for simple code.

TBitmap32.Canvas.pixels[] would return a TColor32 which you can convert using Color32ToRGB function to get the RGB components or Color32ToRGBA to get RGB with alpha. There are separate functions to get just the red, green or blue individually if you are not concerned with the other color components.

BenMorel
  • 34,448
  • 50
  • 182
  • 322
Ali
  • 309
  • 1
  • 3
  • 12