Get pixel color under mouse cursor - FAST way
Asked Answered
S

1

6

Is there ANY way to get pixel color under mouse cursor really FAST? I have a mouse hook and I try to read pixel color during mouse move. Its kind of ColorPicker

Any attempts with getPixel and BitBlt were terribly slow.

UPDATE - ADDED CODE

unit Unit1;

{$mode delphi}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls, lclintf, Windows;

type

  { TForm1 }

  TForm1 = class(TForm)
    pnColor: TPanel;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure ms(var message: tmessage); message WM_USER+1234;
  private
    { private declarations }
  public
    { public declarations }
  end;

var
  Form1: TForm1;
  DC:HDC;

    const WH_MOUSE_LL = 14; //for Lazarus

implementation

{$R *.lfm}

{ TForm1 }

procedure HookMouse(Handle:HWND); stdcall; external 'mhook.dll';
procedure UnHookMouse; stdcall; external 'mhook.dll';

procedure TForm1.FormCreate(Sender: TObject);
begin
  //Self.Caption := IntToStr(Self.Height);
  Self.Left:= Screen.Monitors[0].WorkareaRect.Right  - Self.Width - 18;
  Self.Top := Screen.Monitors[0].WorkareaRect.Bottom - Self.Height - 18 - 25; //35 LAZARUS BUG

  DC := getDC(0);

  HookMouse(Self.Handle);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
    UnHookMouse;
end;

procedure TForm1.ms(var message: tmessage);
var color:TColor;
begin
  color := GetPixel(DC, message.WParam, message.LParam); //<-- Extremly slow
  //format('%d - %d',[message.LParam, message.WParam]); // Edited

  pnColor.Color:=color;
end;

end. 

And the DLL

library project1;

{$mode delphi}{$H+}

uses
  Windows,
  Messages;

var Hook: HHOOK;
    hParent:HWND;

function HookProc(nCode: Integer; MsgID: WParam; Data: LParam): LResult; stdcall;
var
  mousePoint: TPoint;
begin
  //if nCode = HC_ACTION then
  //begin
       mousePoint := PMouseHookStruct(Data)^.pt;
       PostMessage(hParent, WM_USER+1234, mousePoint.X, mousePoint.Y);
  //end;
  Result := CallNextHookEx(Hook,nCode,MsgID,Data);
end;

procedure HookMouse(Parent: Hwnd); stdcall;
begin
  hParent := parent;
  if Hook = 0 then Hook:=SetWindowsHookEx(WH_MOUSE_LL,@HookProc,HInstance,0); 
end;

procedure UnHookMouse; stdcall;
begin
  UnhookWindowsHookEx(Hook);
  Hook:=0;
end;

exports
  HookMouse, UnHookMouse;

begin

end.

UPDATE 2 - One unit update with 100ms interval

unit Unit1;

{$mode delphi}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls, lclintf, Windows;

type

  { TForm1 }

  TForm1 = class(TForm)
    pnColor: TPanel;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
  end;

var
  Form1: TForm1;
  HookHandle: Cardinal;
  DC:HDC;
  timer:Long;

const WH_HOOK_LL = 14; //for Lazarus

implementation

{$R *.lfm}

{ TForm1 }

function LowLevelMouseProc(nCode: Integer; wParam: wParam; lParam: lParam): LRESULT; stdcall;
var
   point:TPoint;
begin
  if (nCode >= 0) then
  begin
    if(GetTickCount - timer >= 100) then
    begin
       point:=PMouseHookStruct(lParam)^.pt;
       Form1.pnColor.Color := GetPixel(DC,point.X,point.Y);
       timer := GetTickCount;
    end;
  end;
  Result := CallNextHookEx(HookHandle, nCode, wParam, lParam);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  //Self.Caption := IntToStr(Self.Height);
  Self.Left:= Screen.Monitors[0].WorkareaRect.Right  - Self.Width - 18;
  Self.Top := Screen.Monitors[0].WorkareaRect.Bottom - Self.Height - 18 - 25; //35 LAZARUS BUG

  DC :=  GetWindowDC(GetDesktopWindow);
  if HookHandle = 0 then
  begin
    HookHandle := SetWindowsHookEx(WH_HOOK_LL, @LowLevelMouseProc, hInstance, 0);
  end;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
    if HookHandle <> 0 then
    UnhookWindowsHookEx(HookHandle);

    ReleaseDC(GetDesktopWindow(), DC);
end;

end.
Scrimpy answered 1/3, 2013 at 10:29 Comment(12)
This is not a Delphi question, or a Lazarus question, and its weird to tag them both anyway. This is a winapi question. I suggest that you tag it that way. And also explain that you mean that you wish to read pixels from the screen rather than from your own app.Trefler
And that is exactly the reason why I added Lazarus tagScrimpy
What's the PostMessage in your code all about? Are you sure that the delay is on GetPixel and not on PostMessage? The low-level mouse hook runs on the thread that installed it.Trefler
Yep, I am sure. One line comment and Voila. Test app works perfectly with only displaying actual mouse coords. However I've just tested bummi's one unit example. A bit faster but not too much. Whole system is slowed down.Scrimpy
And what happens when you call GetPixel but don't modify the panel color?Trefler
GetPixel is slow as with hardware acceleration on the display adapter the OS no longer holds a copy of the screen, but if its a sort of colour picker why do you need to get the colour on each mousemove event, and not just use getcursorpos and get pixel every 100ms or soPollux
@Pollux Or remember the last time that GetPixel was called and skip the call to GetPixel if not enough time has passed. That would avoid lots of timer messages if the cursor was not moving at all.Trefler
look at "TCanvas.ScanLine", please use GetWindowDC(GetDesktopWindow) in stead of GetWindowDC(0);Andri
Thx all. It's about the "feel and responsiveness". I have similar tool here (written propably in VB). Scanline is not available in Lazaurus and I didnt find any clues how to use alternatives. Other screenshot variants were slow as hell too. HookFunc shouldnt be fired when cursor is not moving. Some time skiping would be a solution. I'll give it try.Scrimpy
I have done 'real-time' colour picking on the desktop, and have not observed any bad performance.Lissie
Adding 100ms between readings is great relieve for system. However latency between mouse move and color update is still bad. Lower interval does not affect it. Its better, usable but with "bad user-experience"Scrimpy
@Jan Pfeifer: I think you're moving your mouse too fastPractice
A
6

I wouldn't personally use a hook for this. I would use e.g. a timer with interval 30ms for instance and use the following code to determine position and color of the current pixel under the mouse cursor (the code will work only on Windows platform as your original code can). I'd use this, as because if your application won't be able to process (low level idle priority though) WM_TIMER messages, I don't think it will be able to process so frequent callbacks from your hook keeping the user interface responsible (to process own main thread messages):

unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls,
  StdCtrls, Windows;

type

  { TForm1 }

  TForm1 = class(TForm)
    Label1: TLabel;
    Panel1: TPanel;
    UpdateTimer: TTimer;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure UpdateTimerTimer(Sender: TObject);
  private
    DesktopDC: HDC;
  public
    { public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
begin
  DesktopDC := GetDC(0);
  if (DesktopDC <> 0) then
    UpdateTimer.Enabled := True;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  ReleaseDC(GetDesktopWindow, DesktopDC);
end;

procedure TForm1.UpdateTimerTimer(Sender: TObject);
var
  CursorPos: TPoint;
begin
  if GetCursorPos(CursorPos) then
  begin
    Label1.Caption := 'Cursor pos: [' + IntToStr(CursorPos.x) + '; ' +
      IntToStr(CursorPos.y) + ']';
    Panel1.Color := GetPixel(DesktopDC, CursorPos.x, CursorPos.y);
  end;
end;

end.
Antimicrobial answered 4/3, 2013 at 0:18 Comment(3)
Thank you, using timer was my first attempt. It has quite impact on system performance so I came to hook. However with this settings it is propably the best result (update time vs global performance) I can get. My goal was to achieve something like Litschi-soft's ColGet. So if there is nothing faster than GetPixel this will be an answer.Scrimpy
I think there are ways working directly with graphic adapter, but it would be over-complication of your task (sure it depends on how much time you want to spend on your project). The GetPixel function is quite slow, but it doesn't mean that mouse hook will help it somehow. On the contrary, you've been updating cursor position and color information even more frequently, what makes you feel it's even worse. Human eye can't read so frequently (every single cursor move) and for a good look and feel is IMHO enough a 30ms timer.Antimicrobial
I also intended to add an action on global mouse click. But whatever I tried the best result was achieved only with 30ms timer.Scrimpy

© 2022 - 2024 — McMap. All rights reserved.