Recent

Author Topic: [Solved] GetDC(0) only yields primary screen in Windows 10 (Linux is all OK)  (Read 8315 times)

fripster

  • Newbie
  • Posts: 3
Dear All,

I am working on a simple screenshotting tool that continuously screenshots a screen and then publishes the picture to a webserver. The problem i find is that using the example code from the Lazarus site (which uses getDC(0) to get the device context) does not work under Windows 10. It gets me a screenshot allright, but only shows the primary screen. This should not happen as GetDC(0) normally should yield the complete virtual desktop, including all screens. The same code works perfectly under Linux (Debian with XFCE) and shows all screens.
I have searched the net a lot (even tried TBGRABitmap, same result) and have not found this error anywhere.
Can any of you please point me in the right direction?

Thanks!

Fripster
« Last Edit: January 08, 2019, 08:06:29 pm by fripster »

Thaddy

  • Hero Member
  • *****
  • Posts: 14201
  • Probably until I exterminate Putin.
Re: GetDC(0) only yields primary screen in Windows 10 (Linux is all OK)
« Reply #1 on: January 02, 2019, 07:25:58 pm »
The problem i find is that using the example code from the Lazarus site (which uses getDC(0) to get the device context) does not work under Windows 10. It gets me a screenshot allright, but only shows the primary screen. This should not happen as GetDC(0) normally should yield the complete virtual desktop, including all screens.
I do not know what you have been smoking Tripster, but  GetDc(0) is a Windows API call in the first place and it is proxied for other platforms in Lazarus for convenience.
It gives you a device handle to the primary screen. If the Linux behavior gives you a full virtual screen then the Linux implementation is not correct.
Your assumption is plain wrong.
« Last Edit: January 02, 2019, 07:29:22 pm by Thaddy »
Specialize a type, not a var.

ASerge

  • Hero Member
  • *****
  • Posts: 2222
Re: GetDC(0) only yields primary screen in Windows 10 (Linux is all OK)
« Reply #2 on: January 02, 2019, 08:15:11 pm »
The problem i find is that using the example code from the Lazarus site (which uses getDC(0) to get the device context) does not work under Windows 10.
I only have one monitor, so I can't check. But judging by the articles on the Internet, GetDC(0) gives the entire virtual screen. Perhaps in your utility you forget that the additional monitor may have negative coordinates relative to the primary.

fripster

  • Newbie
  • Posts: 3
Re: GetDC(0) only yields primary screen in Windows 10 (Linux is all OK)
« Reply #3 on: January 02, 2019, 11:52:45 pm »
@Aserge: you are right. GetDC(0) gets the entire virtual screen. I know about the negative numbers, but if i just perform a Tbitmap.SaveToFile function as a test then all screens should be visible in the resulting .bmp file. This only works under Linux (even if the call would be wrong, which I doubt: please see here: http://wiki.freepascal.org/Developing_with_Graphics#Taking_a_screenshot_of_the_screen) but not under Windows ... I think it is strange.

@Thaddy: I do not smoke, and I am sober thank you. For your information and entertainment, please see here: https://stackoverflow.com/questions/14715527/getdcnull-gets-primary-monitor-or-virtual-screen
« Last Edit: January 02, 2019, 11:56:27 pm by fripster »

ASerge

  • Hero Member
  • *****
  • Posts: 2222
Re: GetDC(0) only yields primary screen in Windows 10 (Linux is all OK)
« Reply #4 on: January 04, 2019, 12:17:01 pm »
I know about the negative numbers, but if i just perform a Tbitmap.SaveToFile function as a test then all screens should be visible in the resulting .bmp file. ...please see here: http://wiki.freepascal.org/Developing_with_Graphics#Taking_a_screenshot_of_the_screen) but not under Windows ... I think it is strange.
An old example, before the mass use of multiple monitors. As i see in intfgraphics.pas:
Code: Pascal  [Select][+][-]
  1. procedure TLazIntfImage.LoadFromDevice(DC: HDC);
  2. var
  3.   R: TRect;
  4.   RawImage: TRawImage;
  5.   DeviceSize: TPoint;
  6. begin
  7.   GetDeviceSize(DC, DeviceSize);
  8.   R := Rect(0,0,DeviceSize.X,DeviceSize.Y);
  9. ...
As you can see, negative coordinates are not taken into account.

fripster

  • Newbie
  • Posts: 3
Okay... After some asking around and Googling a lot I finally found how it needs to be done. I will put it here so that others can benefit from it.
Code: Pascal  [Select][+][-]
  1. Procedure TakeScreenShot(VAR mypic:Graphics.TBitmap);
  2. {$IFDEF WINDOWS}
  3.  var
  4.   ScreenDC: HDC;
  5. begin
  6.   mypic.Width:=Screen.DesktopWidth;
  7.   mypic.Height:=Screen.DesktopHeight;
  8.   mypic.Canvas.Brush.Color := clWhite;
  9.   mypic.Canvas.FillRect(0, 0, mypic.Width, mypic.Height);
  10.   ScreenDC:=GetDC(GetDesktopWindow);
  11.   BitBlt(mypic.Canvas.Handle, 0, 0, mypic.Width, mypic.Height, ScreenDC, Screen.DesktopLeft, Screen.DesktopTop, SRCCOPY);
  12.   ReleaseDC(0, ScreenDC);
  13. end;
  14. {$ENDIF}
  15.  
  16. {$IFDEF LINUX}
  17. var
  18.   ScreenDC: HDC;
  19. begin
  20.   ScreenDC := GetDC(0);
  21.   Mypic.LoadFromDevice(ScreenDC);
  22.   ReleaseDC(0,ScreenDC);
  23. end;
  24. {$ENDIF}      
   

This takes a screenshot of all available screens, whatever their positions w.r.t. each other.

Fripster
« Last Edit: January 08, 2019, 11:09:16 pm by fripster »

Manlio

  • Full Member
  • ***
  • Posts: 162
  • Pascal dev
I will put it here so that others can benefit from it.
Fripster

Thank you Fripster for the code, I was looking exactly for that!

And here is a method I created, based on your code, with the additional option of taking the screenshot of only a specific monitor:

Code: [Select]
// Monitor=-1 takes whole composite area, otherwise only specific monitor
procedure ScreenshotToFile(Filename: string; Monitor: integer);
var
  BMP: Graphics.TBitmap; // graphics.TBitmap, not Windows.TBitmap
  ScreenDC: HDC;
  M: TMonitor;
  W, H, X0, Y0: integer;
begin
  // Initialize coordinates of full composite area
  X0 := Screen.DesktopLeft;
  Y0 := Screen.DesktopTop;
  W  := Screen.DesktopWidth;
  H  := Screen.DesktopHeight;
  // Monitor=-1 takes entire screen, otherwise takes specific monitor
  if (Monitor >= 0) and (Monitor < Screen.MonitorCount) then begin
    M  := Screen.Monitors[Monitor];
    X0 := M.Left;
    Y0 := M.Top;
    W  := M.Width;
    H  := M.Height;
  end;
  // prepare the bitmap
  BMP := Graphics.TBitmap.Create;
  BMP.Width  := W;
  BMP.Height := H;
  BMP.Canvas.Brush.Color := clWhite;
  BMP.Canvas.FillRect(0, 0, W, H);
  ScreenDC := GetDC(GetDesktopWindow);
  // copy the required area:
  BitBlt(BMP.Canvas.Handle, 0, 0, W, H, ScreenDC, X0, Y0, SRCCOPY);
  ReleaseDC(0, ScreenDC);
  // save to file (possibly to TStream, etc.)
  BMP.SaveToFile(Filename);
  BMP.Free;
end;

manlio mazzon gmail

furious programming

  • Hero Member
  • *****
  • Posts: 853
Attached is a test desktop capture program. Gets an area based on the cursor position — the cursor points to the upper left corner, and the area is the size of the program window. No matter how many screens there are and where the cursor is — it always copies a visible part of the desktop. If the screens have different resolutions, the empty space is filled by the system with black color. However, the content outside the screens is not cleared by the system, so in the program I first fill the entire bitmap in white, and then the visible desktop fragment is copied.

In the attached picture you can see all three places — the main monitor (LCD0), an additional monitor (LCD1) and the empty space filled in black, resulting from the difference in resolution of both screens. In the second attachment is a Lazarus project of this program. Tested on Windows10. Have fun.
« Last Edit: January 28, 2022, 01:58:07 am 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