Capture image from any window

Share your advanced PureBasic knowledge/code with the community.
User avatar
Joakim Christiansen
Addict
Addict
Posts: 2452
Joined: Wed Dec 22, 2004 4:12 pm
Location: Norway
Contact:

Capture image from any window

Post by Joakim Christiansen »

It shows a list of your windows and let you capture screenshots from them. It works perfectly in Windows Vista, but in Windows XP there is an error; it will include other windows in the screenshot if they're over the window you capture... (Could anyone please help me fix this?)

Code: Select all

EnableExplicit

Procedure CaptureImage(hWnd=#False)
  Protected hImage, hdc, ClientRect.RECT
  
  If hWnd
    GetClientRect_(hWnd,@ClientRect)
    With ClientRect
      If \bottom > 0
        hImage = CreateImage(#PB_Any,\right,\bottom)
        hdc = StartDrawing(ImageOutput(hImage))
          BitBlt_(hdc,0,0,\right,\bottom,GetDC_(hWnd),0,0,#SRCCOPY)
          ;ReleaseDC_()
        StopDrawing()
      EndIf
    EndWith
  Else
    ;Capture desktop
  EndIf
  ;GetDCEx_(hWnd,ClientRect,
  ;#DCX_INTERSECTUPDATE|#DCX_VALIDATE
  ProcedureReturn hImage
EndProcedure

; Procedure ImageWindow(hWnd);,Width,Height)
; 
; 
; EndProcedure 

Enumeration ;Windows
  #Main
  #Image
EndEnumeration
Enumeration ;Gadgets
  #Main_WindowList
  #Main_Capture
  #Main_Close
  #Image_Image
EndEnumeration

;Debug 
Structure Image
  ID.l
  Width.l
  Height.l
EndStructure
Global WindowCount, Image.Image, hWnd, Title.s{128}

Procedure EnumWindows(hWnd,lParam)
  Protected Title.s{128}, windowIsOwned, windowStyle, NormalWindow
  
  SendMessage_(hWnd,#WM_GETTEXT,128,@Title)
  
  windowIsOwned = GetWindow_(hWnd,#GW_OWNER) <> 0
  windowStyle = GetWindowLong_(hWnd,#GWL_EXSTYLE)
  NormalWindow = #True
  
  ;Do not allow invisible processes.  
  If Not IsWindowVisible_(hWnd)
    NormalWindow = #False
  EndIf
  
  ;Window must have a caption
  If Title = ""
    NormalWindow = #False
  EndIf
  
  ;Should not have a parent
  If GetParent_(hWnd) <> 0
    NormalWindow = #False
  EndIf
  
;   ;Don't allow unowned tool tips
;   If (windowStyle | #WS_EX_TOOLWINDOW) <> 0 And Not windowIsOwned
;     NormalWindow = #False
;   EndIf
;   
;   ;Don't allow applications with owners
;   If (windowStyle | #WS_EX_APPWINDOW) = 0 And windowIsOwned
;     NormalWindow = #False
;   EndIf
  
  If NormalWindow
    AddGadgetItem(#Main_WindowList,WindowCount,Title)
    SetGadgetItemData(#Main_WindowList,WindowCount,hWnd)
    WindowCount + 1
  EndIf
  
  ProcedureReturn #True
EndProcedure

OpenWindow(#Main,0,0,250,200,"Select window to capture",#PB_Window_SystemMenu|#PB_Window_ScreenCentered)
CreateGadgetList(WindowID(#Main))
ListViewGadget(#Main_WindowList,5,5,240,165)
ButtonGadget(#Main_Capture,5,175,100,20,"Capture")
ButtonGadget(#Main_Close,145,175,100,20,"Close")

EnumWindows_(@EnumWindows(),#Null)

Repeat
  Select WaitWindowEvent()
    Case #PB_Event_CloseWindow
      Select EventWindow()
        Case #Main
          Break
        Case #Image
          CloseWindow(#Image)
      EndSelect
    Case #PB_Event_Gadget
      Select EventGadget()
        Case #Main_Capture
          If GetGadgetState(#Main_WindowList) > -1
            hWnd = GetGadgetItemData(#Main_WindowList,GetGadgetState(#Main_WindowList))
            Image\ID     = CaptureImage(hWnd)
            If Image\ID
              Image\Width  = ImageWidth(Image\ID)
              Image\Height = ImageHeight(Image\ID)
              GetWindowText_(hWnd,@Title,128)
              OpenWindow(#Image,0,0,Image\Width,Image\Height,"Screenshot - "+Title,#PB_Window_SystemMenu|#PB_Window_ScreenCentered)
              CreateGadgetList(WindowID(#Image))
              ImageGadget(#Image_Image,0,0,Image\Width,Image\Height,ImageID(Image\ID))
            Else
              MessageRequester("Error","Unable to capture image, window may be minimized...",#MB_ICONINFORMATION)
            EndIf
          EndIf
        Case #Main_Close
          Break
      EndSelect
  EndSelect
ForEver
I like logic, hence I dislike humans but love computers.
PB
PureBasic Expert
PureBasic Expert
Posts: 7581
Joined: Fri Apr 25, 2003 5:24 pm

Re: Capture image from any window

Post by PB »

> Procedure CaptureImage(hWnd=#False)
> Protected hImage, hdc, ClientRect.RECT
> If hWnd

The "If" above is pointless for a start, because you're always going to passing
a valid hWnd to the procedure, aren't you? I think you should change it to:

If IsWindow_(hWnd)

to see if the window is still "alive" at the time the procedure is called.
I compile using 5.31 (x86) on Win 7 Ultimate (64-bit).
"PureBasic won't be object oriented, period" - Fred.
User avatar
Joakim Christiansen
Addict
Addict
Posts: 2452
Joined: Wed Dec 22, 2004 4:12 pm
Location: Norway
Contact:

Re: Capture image from any window

Post by Joakim Christiansen »

Actually I was going to make the procedure so it captures the desktop if you pass an empty hWnd to it. But yes I agree that IsWindow_(hWnd) is smart to check...
I like logic, hence I dislike humans but love computers.
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post by netmaestro »

in Windows XP there is an error; it will include other windows in the screenshot if they're over the window you capture... (Could anyone please help me fix this?)
You can capture the client area of any window whose handle you know, using this tecnique:

Code: Select all

GetClientRect_(windowhandle, @cr.RECT)
CreateImage(0, cr\right-cr\left, cr\bottom-cr\top, 24)
hdc=StartDrawing(ImageOutput(0)) 
  SendMessage_(windowhandle, #WM_PRINT, hdc, #PRF_CLIENT|#PRF_CHILDREN|#PRF_ERASEBKGND|#PRF_CHECKVISIBLE )
StopDrawing() 
Done this way, it doesn't matter what other windows are obscuring it, it only draws its own client area to the image. Basically, the message is asking the window to redraw itself to the specified dc instead of its own.
BERESHEIT
User avatar
Joakim Christiansen
Addict
Addict
Posts: 2452
Joined: Wed Dec 22, 2004 4:12 pm
Location: Norway
Contact:

Post by Joakim Christiansen »

Just tried that now by changing this line:

Code: Select all

BitBlt_(hdc,0,0,\right,\bottom,GetDC_(hWnd),0,0,#SRCCOPY)
into this:

Code: Select all

SendMessage_(hWnd,#WM_PRINT,hdc,#PRF_CLIENT|#PRF_CHILDREN|#PRF_ERASEBKGND|#PRF_CHECKVISIBLE)
Can't say it gave me good results... (At least not in Vista where I tried it...)
I like logic, hence I dislike humans but love computers.
Post Reply