wxWidgets / Phoenix

wxPython's Project Phoenix. A new implementation of wxPython, better, stronger, faster than he was before.
http://wxpython.org/
2.33k stars 515 forks source link

Multiple screenshots using ScreenDC issue #259

Closed proggeo closed 7 years ago

proggeo commented 7 years ago

If you take a screenshot with wx.ScreenDC() for few times, then every time you will get the same image: the screenshot at the moment when wx.ScreenDC() was called for the first time. The only solution found is to run it every time in separate Process with its own wx.App() inside.

However, if you do this on wxPython Classic, it works just fine. Sample code down here:

def take_screenshot():
    screen = wx.ScreenDC()
    size = screen.GetSize()
    width = size[0]
    height = size[1]
    bmp = wx.Bitmap(width, height)
    mem = wx.MemoryDC(bmp)
    mem.Blit(0, 0, width, height, screen, 0, 0)
    bmp.SaveFile(str(datetime.now()) + '.png', wx.BITMAP_TYPE_PNG)

if __name__ == '__main__':
    app = wx.App()
    take_screenshot()
    sleep(3)
    take_screenshot()
    sleep(3)
    take_screenshot()
    sleep(3)
    take_screenshot()
nepix32 commented 7 years ago

Has been asked (with possible working answer) on Stackoverflow.

ncqgm commented 5 years ago

I do not think this issue is solved: http://wxpython-users.1045709.n5.nabble.com/wx-ScreenDC-screenshotting-problems-td5730158.html Please re-check. Thanks, Karsten

swt2c commented 5 years ago

I see this also with Classic (but built against GTK 3), so I think this is probably rather something to do with the GTK 3 implementation of wxScreenDC and nothing wxPython specific. Probably needs a C++ reproducer and ticket opened on trac.wxwidgets.org.

RobinD42 commented 5 years ago

Yep, there are also similar problems with OSX.

My understanding is that due to the additional layers between the applications and the display for automatic buffering and compositing, that we don't have the same level of access to the display's image buffer that we had before with the older systems, and so we simply can not fetch what is displayed on the screen via the wx.ScreenDC API in order to Blit it into a bitmap. I think I saw somebody say that you can think of wx.ScreenDC being a write-only interface now.

That said, on OSX you can use wx.ScreenDC.GetAsBitmap to get an image of the screen. It's not implemented for GTK3 unfortunately.

ncqgm commented 5 years ago

On Mon, Aug 12, 2019 at 06:29:06PM -0700, Robin Dunn wrote:

Yep, there are also similar problems with OSX.

My understanding is that due to the additional layers between the applications and the display for automatic buffering and compositing, that we don't have the same level of access to the display's image buffer that we had before with the older systems, and so we simply can not fetch what is displayed on the screen via the wx.ScreenDC API in order to Blit it into a bitmap.

I feared as much. Put another way: there's no way to fully (including window decorations) screenshot an application from within wxPhoenix ?

Karsten

GPG 40BE 5B0E C98E 1713 AFA6 5BC0 3BEA AC80 7D4F C89B

infinity77 commented 5 years ago

If you are willing to try other packages, then pyscreenshot might be a way:

https://github.com/ponty/pyscreenshot

Since apparently they have done it, I don’t see why it shouldn’t be possible in wx. Also, doesn’t wxWidgets have its own screenshot tool for the documentation:

https://github.com/wxWidgets/wxWidgets/blob/cc931612eec2e3ea49200ebff45042135f3c3f9c/utils/screenshotgen/src/autocapture.cpp

Andrea.

On Tue, 13 Aug 2019 at 08.27, ncqgm notifications@github.com wrote:

On Mon, Aug 12, 2019 at 06:29:06PM -0700, Robin Dunn wrote:

Yep, there are also similar problems with OSX.

My understanding is that due to the additional layers between the applications and the display for automatic buffering and compositing, that we don't have the same level of access to the display's image buffer that we had before with the older systems, and so we simply can not fetch what is displayed on the screen via the wx.ScreenDC API in order to Blit it into a bitmap.

I feared as much. Put another way: there's no way to fully (including window decorations) screenshot an application from within wxPhoenix ?

Karsten

GPG 40BE 5B0E C98E 1713 AFA6 5BC0 3BEA AC80 7D4F C89B

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/wxWidgets/Phoenix/issues/259?email_source=notifications&email_token=ACESNIN7I46O2RZ6VLAJSI3QEJH63A5CNFSM4DFBOMDKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD4EVSCA#issuecomment-520706312, or mute the thread https://github.com/notifications/unsubscribe-auth/ACESNILULMFGA7W467NKPKTQEJH63ANCNFSM4DFBOMDA .

ncqgm commented 5 years ago

On Tue, Aug 13, 2019 at 06:43:02AM +0000, Andrea Gavana wrote:

Also, doesn’t wxWidgets have its own screenshot tool for the documentation:

https://github.com/wxWidgets/wxWidgets/blob/cc931612eec2e3ea49200ebff45042135f3c3f9c/utils/screenshotgen/src/autocapture.cpp

I saw that before but looked again:

AFAICS they do exactly what I do, except for a

memory_DC.Clear()

after setting the bitmap object on the memory_DC:

bmp = wx.Bitmap()
memory_DC.SelectObj(bmp)
memory_DC.Clear()                       # <==
memory_DC.Blit(..., screen_DC, ...)
memory_DC.SelectObj(wx.NullBitmap)
bmp.SaveFile()

I can't see how this should make a difference but I'll give it a try. That sequence (w/o the .Clear()) certainly does not work for me.

Karsten

GPG 40BE 5B0E C98E 1713 AFA6 5BC0 3BEA AC80 7D4F C89B

ncqgm commented 5 years ago

On Tue, Aug 13, 2019 at 06:43:02AM +0000, Andrea Gavana wrote:

If you are willing to try other packages, then pyscreenshot might be a way:

https://github.com/ponty/pyscreenshot

They are doing it the brutal way: Creating a new wx.App for each screenshot.

Karsten

GPG 40BE 5B0E C98E 1713 AFA6 5BC0 3BEA AC80 7D4F C89B

ncqgm commented 5 years ago

On Mon, Aug 12, 2019 at 06:29:06PM -0700, Robin Dunn wrote:

My understanding is that due to the additional layers between the applications and the display for automatic buffering and compositing, that we don't have the same level of access to the display's image buffer that we had before with the older systems, and so we simply can not fetch what is displayed on the screen via the wx.ScreenDC API in order to Blit it into a bitmap. I think I saw somebody say that you can think of wx.ScreenDC being a write-only interface now.

I am not so sure anymore that I understand that line of thought:

Upon the first instantiation of wx.ScreenDC() it works just right - the ScreenDC contains a copy of the desktop at that time.

Subsequent, new, instantiations of the same class will not.

Why would one have access to the desktop content the first time around but not later on ?

We are not talking live interaction with the desktop here.

Why should it be technically possible to read once but not more than once ?

Surely that sounds like the first instantiation is cached somewhere and merely returned later on ?

Best, Karsten

GPG 40BE 5B0E C98E 1713 AFA6 5BC0 3BEA AC80 7D4F C89B

The-o commented 3 years ago

Faced the same problem on Ubuntu 19.10, wxPython 4.0.6 gtk3 (phoenix) wxWidgets 3.0.4, python 3.7.5. And it seems I found workaround:

Just insert screen.Blit(0, 0, width, height, screen, 0, 0) after mem.Blit(0, 0, width, height, screen, 0, 0). So the working (for my setup) version of the code is:

import wx
from datetime import datetime
from time import sleep

def take_screenshot():
    screen = wx.ScreenDC()
    size = screen.GetSize()
    width = size[0]
    height = size[1]

    bmp = wx.Bitmap(width, height)
    mem = wx.MemoryDC(bmp)
    mem.Blit(0, 0, width, height, screen, 0, 0)

    screen.Blit(0, 0, width, height, screen, 0, 0) # <-- this line makes multiple screenshots work

    bmp.SaveFile(str(datetime.now()) + '.png', wx.BITMAP_TYPE_PNG)

if __name__ == '__main__':
    app = wx.App()
    take_screenshot()
    sleep(3)
    take_screenshot()
    sleep(3)
    take_screenshot()
    sleep(3)
    take_screenshot()

upd:

It is better to use wx.OR logical function in screen-on-screen blitting (screen.Blit(0, 0, width, height, screen, 0, 0, wx.OR)), because oherwise in Windows it paints the screen white.

ncqgm commented 3 years ago

Am Thu, Mar 18, 2021 at 02:56:51AM -0700 schrieb The-o:

Faced the same problem on Ubuntu 19.10, wxPython 4.0.6 gtk3 (phoenix) wxWidgets 3.0.4, python 3.7.5. And it seems I found workaround:

Just insert screen.Blit(0, 0, width, height, screen, 0, 0) after mem.Blit(0, 0, width, height, screen, 0, 0).

I can confirm this works for me. It perhaps "updates" a globally cached (?) Screen DC from layers further down, closer to the hardware ?

Karsten

The-o commented 3 years ago

It perhaps "updates" a globally cached (?) Screen DC from layers further down, closer to the hardware ?

I don't really know how and why it works. I believe it is something to do with wxWidgets and not with wxPython itself. I just saw that thare was a different if-branch in the latest wxWidgets GTK DC implementation when the source and the destination DCs were the same (https://github.com/wxWidgets/wxWidgets/blob/v3.1.4/src/gtk/dc.cpp#L152). Then the idea of trying to repaint screen with its own content came to me. And it surprisingly worked.

But the confusing fact is that there is no such an if-branch in wxWidgets v3.0.4 I have (https://github.com/wxWidgets/wxWidgets/blob/v3.0.4/src/gtk/dc.cpp#L138). And I'm not good at cairo, so the mystery of why the workaround works still remains for me.