Zrzucanie ekranu z kursorem myszki

0

Piszę program, który wykorzystuje fragment ekranu. Chodzi o to, że wskazuje sie współrzędne tego fragmentu i zwraca się obraz tego fragmentu ekranu w bitmapie.

Kiedyś napisałem dwie wersje funkcji. Różnią sie prezentowaniem kursora myszki. Jedna to taka, gdzie tylko bierze położenie wskaźnika myszy i rysuje krzyżak. Krzyżak jest zawsze bez względu na wygląd kursora myszki. Zmienne Grub, Dlug, Ends to parametry krzyżaka, które ustawia się w formularzu.


function CaptureScreen(XXX1,YYY1,XXX2,YYY2:LongInt): TBitmap;
var XXXXX,YYYYY:LongInt;
    R,G,B:Byte;
    RGBindex:LongInt;
    Grub,Dlug,Ends:Integer;
begin
 DC := GetDC(GetDesktopWindow);
 try
  ABitmap.Width  := XXX2;
  ABitmap.Height := YYY2;

  ABitmap.Canvas.Pen.Color:=FormMain.ScreenColor;
  ABitmap.Canvas.Brush.Color:=FormMain.ScreenColor;
  ABitmap.Canvas.Rectangle(0,0,ABitmap.Width,ABitmap.Height);

  BitBlt(ABitmap.Canvas.Handle,00,00,XXX2,YYY2,DC,XXX1,YYY1,SRCCOPY);

  If IsMouse Then
  Begin
   Grub:=FormMain.GrubEdit.Value;
   Dlug:=FormMain.DlugEdit.Value;
   Ends:=FormMain.EndsEdit.Value;
   XXXXX:=Mouse.CursorPos.x-XXX1;
   YYYYY:=Mouse.CursorPos.y-YYY1;

   RGBindex:=ABitmap.Canvas.Pixels[XXXXX,YYYYY];
   R:=((StringToColor('$0000'+Copy(IntToHex(RGBindex,6),5,2)+''))   div   1);
   G:=((StringToColor('$00'+Copy(IntToHex(RGBindex,6),3,2)+'00'))  div  256);
   B:=((StringToColor('$'+Copy(IntToHex(RGBindex,6),1,2)+'0000')) div 65536);

   ABitmap.Canvas.Pen.Color:=RGB(R,G,B);
   ABitmap.Canvas.Brush.Color:=RGB(R,G,B);
   ABitmap.Canvas.Rectangle(XXXXX-Dlug-Grub-Ends,YYYYY-Grub,XXXXX+Dlug+1+Grub+Ends,YYYYY+1+Grub);
   ABitmap.Canvas.Rectangle(XXXXX-Grub,YYYYY-Dlug-Grub-Ends,XXXXX+1+Grub,YYYYY+Dlug+1+Grub+Ends);

   If Ends=(-1) Then
   Begin
    ABitmap.Canvas.Rectangle(0,YYYYY-Grub,ABitmap.Width,YYYYY+1+Grub);
    ABitmap.Canvas.Rectangle(XXXXX-Grub,0,XXXXX+1+Grub,ABitmap.Height);
   End;

   ABitmap.Canvas.Pen.Color:=RGB(255-R,255-G,255-B);
   ABitmap.Canvas.Brush.Color:=RGB(255-R,255-G,255-B);
   ABitmap.Canvas.Rectangle(XXXXX-Dlug-Grub,YYYYY-Grub,XXXXX+Dlug+1+Grub,YYYYY+1+Grub);
   ABitmap.Canvas.Rectangle(XXXXX-Grub,YYYYY-Dlug-Grub,XXXXX+1+Grub,YYYYY+Dlug+1+Grub);

   If Dlug=(-1) Then
   Begin
    ABitmap.Canvas.Rectangle(0,YYYYY-Grub,ABitmap.Width,YYYYY+1+Grub);
    ABitmap.Canvas.Rectangle(XXXXX-Grub,0,XXXXX+1+Grub,ABitmap.Height);
   End;
  End;



 finally
  ReleaseDC(GetDesktopWindow, DC);
 end;
 Result := ABitmap;
end;

Druga wersja pobiera kształt myszki i w obrazku powinien być on odwzorowany, podobnie jak w Lupie Microsoft:


function CaptureScreen(XXX1,YYY1,XXX2,YYY2:LongInt): TBitmap;
var
 PunktX,PunktY:LongInt;
begin
 DC := GetDC(GetDesktopWindow);
 try
  ABitmap.Width  := XXX2;
  ABitmap.Height := YYY2;
  ABitmap.Canvas.Pen.Color:=FormMain.ScreenColor;
  ABitmap.Canvas.Brush.Color:=FormMain.ScreenColor;
  ABitmap.Canvas.Rectangle(0,0,ABitmap.Width,ABitmap.Height);
  BitBlt(ABitmap.Canvas.Handle,00,00,XXX2,YYY2,DC,XXX1,YYY1,SRCCOPY);

  try
   CursorInfo.cbSize := SizeOf(CursorInfo);
   GetCursorInfo(CursorInfo);
   MyCursor.Handle := CursorInfo.hCursor;
  //***************************
   GetIconInfo(CursorInfo.hCursor, IconInfo);
  //***************************
   PunktX:=IconInfo.xHotspot;
   PunktY:=IconInfo.yHotspot;
   ABitmap.Canvas.Draw((Mouse.CursorPos.x-PunktX-XXX1),(Mouse.CursorPos.y-PunktY-YYY1), MyCursor);
   MyCursor.ReleaseHandle;
  finally
  end;
 finally
  ReleaseDC(GetDesktopWindow, DC);
 end;
 Result := ABitmap;
end;


O ile z tą pierwszą wersją wszystko gra, z tą drugą wersją jest problem.
Chodzi o to, że po pewnym czasie (kilka minut) pojawia się komunikat "Out of system resources".
Nie wiem, o co chodzi, ale domyślam się, że chodzi tu o to, że z każdym wywołaniem funkcji jest rezerwowany obszar pamięci, który nie jest zwalniany i w końcu cała pamięć zostaje zarezerwowana wiele razy. Co ciekawe, jak usunę (zamienię w komentarz) linijkę pomiędzy liniami "**********" to problem znika (nie wiem, czy znika, czy tylko wyraźnie przedłuża sie czas do wyczerpania zasobów), ale wtedy nie jest odczytany punk wskazujący w kursorze i na obrazku kursor jest przesunięty w stosunku do oryginału.

Dokładnie rzecz biorąc, pokazuje się komunikat na poniższym obrazku
user image
user image

Co muszę zrobić, żeby ta druga wersja działała bezproblemowo.

A może jest jakiś lepszy sposób, żeby zrzucić ekran lub jego fragment z kursorem myszki?

0

Powinieneś zwolnić:
ABitmap
CursorInfo
MyCursor

Poza tym nie mogę sprawdzić kodu, gdyż podałeś tylko główną funkcję, a odniesienia jak np.: MyCursor są nie znane.

Druga sprawa, zobacz co Ci kompilator podaje, wiele niewykorzystanych zmiennych, a być może i inne błędy.

0

GetIconInfo creates bitmaps for the hbmMask and hbmColor members of ICONINFO. The calling application must manage these bitmaps and delete them when they are no longer necessary.

http://msdn.microsoft.com/en-us/library/ms648070(VS.85).aspx

To powinno Ci pomóc:

procedure TForm1.Button1Click(Sender: TObject);
var winDC, srcdc, destdc : HDC;
oldBitmap : HBitmap;
iinfo : TICONINFO;
begin
GetIconInfo(Image1.Picture.Icon.Handle, iinfo);

WinDC := getDC(handle);
srcDC := CreateCompatibleDC(WinDC);
destDC := CreateCompatibleDC(WinDC);
oldBitmap := SelectObject(destDC, iinfo.hbmColor);
oldBitmap := SelectObject(srcDC, iinfo.hbmMask);

BitBlt(destdc, 0, 0, Image1.picture.icon.width,
Image1.picture.icon.height,
srcdc, 0, 0, SRCPAINT);
Image2.picture.bitmap.handle := SelectObject(destDC, oldBitmap);
DeleteDC(destDC);//<------------------- zwolnienie hbmColor
DeleteDC(srcDC);//<------------------- zwolnienie hbmMask
DeleteDC(WinDC);

image2.Picture.Bitmap.savetofile(ExtractFilePath(Application.ExeName)
+ 'myfile.bmp');
end;
0
Opi napisał(a)

Powinieneś zwolnić:
ABitmap
CursorInfo
MyCursor

Poza tym nie mogę sprawdzić kodu, gdyż podałeś tylko główną funkcję, a odniesienia jak np.: MyCursor są nie znane.

Druga sprawa, zobacz co Ci kompilator podaje, wiele niewykorzystanych zmiennych, a być może i inne błędy.

Zmienne są globalne, a poniżej ich definicja:

ABitmap: TBitmap;
MyCursor: TIcon;
CursorInfo: TCursorInfo;
IconInfo: TIconInfo;
DC: HDC;

Przy rozpoczynaniu pracy programu jest inicjalizacja ABitmap:

ABitmap :=TBitmap.Create;

Jeśli chodzi o sposób użycia procedury, to jest ona wywoływana z timera, którego interwał się ustawia w programie. Ustawieie interwału 1ms powoduje wywołanie procedury raz za razem, że zaraz po zakończeniu jest wywoływana ponownie (nie wiem, co jest wąskim gardłem, użycie procesora nie jest 100%). Oczywiście wspomniana funkcja nie jest podczepiona pod timer, tylko jest ona w procedurze timera razem z innymi poleceniami wykorzystującymi obrazek. Program pracuje w jednym wątku, wiec nie jest możliwe wywołanie kolejnej kopii procedury zanim zakończy się aktualna kopia.

Do testów procedury ustawiałem interwał 1ms (najszybciej wywali, że brak zasobów jeżeli się one powoli zapełniają). Pierwsza wersja procedury (z krzyżakiem zamiast kursora) wydaje się chodzić prawidłowo, zostawiłem program pracujący na ok. 8 godzin. Natomiast w drugiej wersji po ok. 2-3 minutach pojawiał się błąd.

Procedura zrzucająca nie buduje ABitmap, tylko po prostu rysuje na nim na nowo, więc pamięć nie jest zapełniana rysunkami ABitmap. Nie tworzy się więc i nie zwalnia ABitmap.

MyCursor, widać, że jest na bieżąco tworzone (MyCursor := TIcon.Create) i zwalniane (MyCursor.Free). Jednak nie wiem, czy to drugie dać pomiędzy finally i end, czy za blokiem try/finally/end.

Faktycznie jest możliwe, że za każdym razem trzeba zwalniać CursorInfo po zrzuceniu kursora. Ale jak to zrobić? Nie ma CursorInfo.Free czy czegoś podobnego.

Zmienne:
CursorInfo: TCursorInfo;
IconInfo: TIconInfo;
DC: HDC;

zmieniłem na lokalne (przeniosłem ich deklaracje do wnętrza funkcji).

Chciałem też przenieść do wnętrza funkcji zmienną ABitmap, na początku ABitmap:=TBitmap.Create, a na końcu funkcji ABitmap.FreeImage Jednak w przypadku pierwszej wersji po tych zmianach też był problem z zasobami, po cofnięciu zmian nie było problemu. Myśląc podobnie, zmienną MyCursor przeniosłem do zmiennej globalnej i też jest inicjalizowana (MyCursor:=TIcon.Create) tylko raz, przy uruchamianiu programu. Ale to i tak nie rozwiązuje problemu z wolnymi zasobami w przypadku wywoływania drugiej wersji funkcji.

Obecny kształt funkcji, z którą mam problem.



function CaptureScreen(XXX1,YYY1,XXX2,YYY2:LongInt): TBitmap;
var
  CursorInfo: TCursorInfo;
  IconInfo: TIconInfo;
  DC: HDC;
  PunktX,PunktY:LongInt;
begin
 DC := GetDC(GetDesktopWindow);
 try
  ABitmap.Width  := XXX2;
  ABitmap.Height := YYY2;
  ABitmap.Canvas.Pen.Color:=FormMain.ScreenColor;
  ABitmap.Canvas.Brush.Color:=FormMain.ScreenColor;
  ABitmap.Canvas.Rectangle(0,0,ABitmap.Width,ABitmap.Height);
  BitBlt(ABitmap.Canvas.Handle,00,00,XXX2,YYY2,DC,XXX1,YYY1,SRCCOPY);

  try
   CursorInfo.cbSize := SizeOf(CursorInfo);
   GetCursorInfo(CursorInfo);
   MyCursor.Handle := CopyIcon(CursorInfo.hCursor);
   GetIconInfo(CursorInfo.hCursor, IconInfo);
   PunktX:=IconInfo.xHotspot;
   PunktY:=IconInfo.yHotspot;
   ABitmap.Canvas.Draw((Mouse.CursorPos.x-PunktX-XXX1),(Mouse.CursorPos.y-PunktY-YYY1), MyCursor);
   MyCursor.ReleaseHandle;
  finally
  end;
 finally
 end;
 ReleaseDC(GetDesktopWindow, DC);
 Result := ABitmap;
end;




0

Przecież napisałem Ci 2 posty wyżej, ze musisz zwolnić hbmMask oraz hbmColor, które są alokowane przez funkcję GetIconInfo .

var
winDC, srcdc, destdc : HDC;
oldBitmap : HBitmap;
iinfo : TICONINFO;
begin
GetIconInfo(Image1.Picture.Icon.Handle, iinfo);

(...)
oldBitmap := SelectObject(destDC, iinfo.hbmColor);
oldBitmap := SelectObject(srcDC, iinfo.hbmMask);

DeleteDC(destDC);//<------------------- zwolnienie hbmColor
DeleteDC(srcDC);//<------------------- zwolnienie hbmMask

Ech, kłania się czytanie ze zrozumieniem....

0

Próbuję wykorzystać to, co piszesz, ale wciąż nie działa


function CaptureScreen(XXX1,YYY1,XXX2,YYY2:LongInt): TBitmap;
var
  CursorInfo: TCursorInfo;
  IconInfo: TIconInfo;
  DC: HDC;
  PunktX,PunktY:LongInt;
  srcdc, destdc : HDC;
  oldBitmap : HBitmap;
begin
 DC := GetDC(GetDesktopWindow);
// try
  ABitmap.Width  := XXX2;
  ABitmap.Height := YYY2;
  ABitmap.Canvas.Pen.Color:=FormMain.ScreenColor;
  ABitmap.Canvas.Brush.Color:=FormMain.ScreenColor;
  ABitmap.Canvas.Rectangle(0,0,ABitmap.Width,ABitmap.Height);

  CursorInfo.cbSize := SizeOf(CursorInfo);
  GetCursorInfo(CursorInfo);
  MyCursor.Handle := CopyIcon(CursorInfo.hCursor);
  GetIconInfo(CursorInfo.hCursor, IconInfo);

  srcDC := CreateCompatibleDC(DC);
  destDC := CreateCompatibleDC(DC);
  oldBitmap := SelectObject(destDC, IconInfo.hbmColor);
  oldBitmap := SelectObject(srcDC, IconInfo.hbmMask);

  BitBlt(ABitmap.Canvas.Handle,00,00,XXX2,YYY2,DC,XXX1,YYY1,SRCCOPY);


  PunktX:=IconInfo.xHotspot;
  PunktY:=IconInfo.yHotspot;
  ABitmap.Canvas.Draw((Mouse.CursorPos.x-PunktX-XXX1),(Mouse.CursorPos.y-PunktY-YYY1), MyCursor);
// finally
// end;
 MyCursor.ReleaseHandle;
 DeleteDC(destDC);
 DeleteDC(srcDC);
// ReleaseDC(GetDesktopWindow, DC);
 DeleteDC(DC);
 Result := ABitmap;
end;

Poza tym, jak wyświetlić na ekranie ilość wolnych zasobów? Chodzi o to, że będzie można zauważyć stopniowe ubywanie, żeby nie czekać, czy wyskoczy błąd.

0

W Internecie znalazłem, że jest polecenie DeleteObject. Chciałem tego spróbować do usuwania tych dwóch bitmap. Niby działa, żadnego błedu nie wywala, ale po pewnym czasie przestaje zrzucać kursor myszki. Chodzi o to, że program po iluś racach przestaje wrzucać kształt kursora do zmiennej MyCursor. Odkryłem, że GetIconInfo nie jest tego przyczyną.

function CaptureScreen(XXX1,YYY1,XXX2,YYY2:LongInt): TBitmap;
var
  CursorInfo: TCursorInfo;
  IconInfo: TIconInfo;
  DC: HDC;
  PunktX,PunktY:LongInt;
begin
 DC := GetDC(GetDesktopWindow);
 ABitmap.Width  := XXX2;
 ABitmap.Height := YYY2;
 ABitmap.Canvas.Pen.Color:=FormMain.ScreenColor;
 ABitmap.Canvas.Brush.Color:=FormMain.ScreenColor;
 ABitmap.Canvas.Rectangle(0,0,ABitmap.Width,ABitmap.Height);

 CursorInfo.cbSize := SizeOf(CursorInfo);
 GetCursorInfo(CursorInfo);
 MyCursor.Handle := CopyIcon(CursorInfo.hCursor);
 GetIconInfo(CursorInfo.hCursor, IconInfo);


 BitBlt(ABitmap.Canvas.Handle,00,00,XXX2,YYY2,DC,XXX1,YYY1,SRCCOPY);


 PunktX:=IconInfo.xHotspot;
 PunktY:=IconInfo.yHotspot;
 ABitmap.Canvas.Draw((Mouse.CursorPos.x-PunktX-XXX1),(Mouse.CursorPos.y-PunktY-YYY1), MyCursor);
 DeleteObject(IconInfo.hbmColor);
 DeleteObject(IconInfo.hbmMask);
 MyCursor.ReleaseHandle;
 ReleaseDC(GetDesktopWindow, DC);
 DeleteDC(DC);
 Result := ABitmap;
end;

Edit:

Udało mi się dojść, pracowało bez przerwy przez 4 godziny i żadnych problemów nie było:

function CaptureScreen(XXX1,YYY1,XXX2,YYY2:LongInt): TBitmap;
var
  CursorInfo: TCursorInfo;
  IconInfo: TIconInfo;
  DC: HDC;
  PunktX,PunktY:LongInt;
  MouseX,MouseY:LongInt;
  SX1,SX2,SY1,SY2:Integer;
begin
 MouseX:=Mouse.CursorPos.x;
 MouseY:=Mouse.CursorPos.y;
 DC := GetDC(GetDesktopWindow);
 ABitmap.Width  := XXX2;
 ABitmap.Height := YYY2;
 ABitmap.Canvas.Pen.Color:=FormMain.ScreenColor;
 ABitmap.Canvas.Brush.Color:=FormMain.ScreenColor;
 ABitmap.Canvas.Rectangle(0,0,ABitmap.Width,ABitmap.Height);

 CursorInfo.cbSize := SizeOf(CursorInfo);
 GetCursorInfo(CursorInfo);
 MyCursor.Handle := (CursorInfo.hCursor);
 GetIconInfo(CursorInfo.hCursor, IconInfo);

 BitBlt(ABitmap.Canvas.Handle,00,00,XXX2,YYY2,DC,XXX1,YYY1,SRCCOPY);

 PunktX:=IconInfo.xHotspot;
 PunktY:=IconInfo.yHotspot;

 ABitmap.Canvas.Draw(MouseX-PunktX-XXX1,MouseY-PunktY-YYY1, MyCursor);


 DeleteObject(IconInfo.hbmColor);
 DeleteObject(IconInfo.hbmMask);

 ReleaseDC(GetDesktopWindow, DC);
 DeleteDC(DC);
 Result := ABitmap;
end;

1 użytkowników online, w tym zalogowanych: 0, gości: 1