Image from client screen produces black image

I have seen this code sequence described in various incarnations on these forums and is purported to work. However, most of the developers using it are on windows. I saw a vague reference somewhere that perhaps this does not work on OS X with retina displays. In fact, I am running on the latest Catalina release on a 2016 MBP. Running this code on Python 3.8.2 and wxpython 4.1.0 produces an image file (.png, .bmp, or .jpg) that is totally black. Am I missing something here:

            window:  ScrolledWindow = self.getCurrentFrame()
            context: ClientDC = ClientDC(window)
            memory:  MemoryDC = MemoryDC()

            x, y = window.ClientSize
            emptyBitmap: Bitmap = Bitmap(x, y, -1)

            memory.SelectObject(emptyBitmap)
            memory.Blit(source=context, xsrc=0, height=y, xdest=0, ydest=0, ysrc=0, width=x)
            memory.SelectObject(NullBitmap)

           extension: str = 'png'
           imageType: BitmapType = BITMAP_TYPE_PNG

            img:      Image = emptyBitmap.ConvertToImage()
            filename: str   = f'DiagramDump.{extension}'
            status:   bool  = img.SaveFile(filename, imageType)

In an effort to get more help, I created the smallest possible program that will illustrate this. It is here:

Test program

Note I am using 4.0.7 on OpenSuse 15.1 python3.6
wx._core.wxAssertionError: C++ assertion “id == wxID_ANY || (id >= 0 && id < 32767) || (id >= wxID_AUTO_LOWEST && id <= wxID_AUTO_HIGHEST)” failed at /home/abuild/rpmbuild/BUILD/wxPython-4.0.7.post1/ext/wxWidgets/src/common/wincmn.cpp(372) in CreateBase(): invalid id value

Using 4.1.x on Ubuntu 20 python 3.8.x I get the same message:

wx._core.wxAssertionError: C++ assertion “id == wxID_ANY || (id >= 0 && id < 32767) || (id >= wxID_AUTO_LOWEST && id <= wxID_AUTO_HIGHEST)” failed at /home/wxpy/wxPython-4.1.0/ext/wxWidgets/src/common/wincmn.cpp(375) in CreateBase(): invalid id value

Can you tell which widget is issuing this assertion?

A little debugging I realized that you were setting the following:
FRAME_ID: int = 0xDeadBeef
I imported wx
and
FRAME_ID: int = wx.ID_ANY
That then creates a button at the upper left, and pic in the center of the frame and I can move it with the mouse. BTW this worked with both wxPython 4.0.7 and 4.1

So when you press the “Draw Me” button do you see a valid file called DiagramDump.png and it is a picture of the diagram area?

No, I do not see a saved file. But I also believe the img.SaveFile(filename, imageType) is using the core.pyi(r/o) Image->SaveFile() which does nothing. I normally do not work with images and could be talking out of my backside. So take this with a grain of salt.

1 Like

try this (got it from[http://www.blog.pythonlibrary.org/2010/04/16/how-to-take-a-screenshot-of-your-wxpython-app-and-print-it/]

def onTakeScreenShot(self, event):
“”" Takes a screenshot of the screen at give pos & size (rect). “”"
print (‘Taking screenshot…’)
rect = self.GetRect()
# see http://aspn.activestate.com/ASPN/Mail/Message/wxpython-users/3575899
# created by Andrea Gavana

    # adjust widths for Linux (figured out by John Torres 
    # http://article.gmane.org/gmane.comp.python.wxpython/67327)
    if sys.platform == 'linux2':
        client_x, client_y = self.ClientToScreen((0, 0))
        border_width = client_x - rect.x
        title_bar_height = client_y - rect.y
        rect.width += (border_width * 2)
        rect.height += title_bar_height + border_width
    #Create a DC for the whole screen area
    dcScreen = wx.ScreenDC()
    #Create a Bitmap that will hold the screenshot image later on
    #Note that the Bitmap must have a size big enough to hold the screenshot
    #-1 means using the current default colour depth
    bmp = wx.EmptyBitmap(rect.width, rect.height)
    #Create a memory DC that will be used for actually taking the screenshot
    memDC = wx.MemoryDC()
    #Tell the memory DC to use our Bitmap
    #all drawing action on the memory DC will go to the Bitmap now
    memDC.SelectObject(bmp)
    #Blit (in this case copy) the actual screen on the memory DC
    #and thus the Bitmap
    memDC.Blit( 0, #Copy to this X coordinate
                0, #Copy to this Y coordinate
                rect.width, #Copy this width
                rect.height, #Copy this height
                dcScreen, #From where do we copy?
                rect.x, #What's the X offset in the original DC?
                rect.y  #What's the Y offset in the original DC?
                )
    #Select the Bitmap out of the memory DC by selecting a new
    #uninitialized Bitmap
    memDC.SelectObject(wx.NullBitmap)
    img = bmp.ConvertToImage()
    fileName = "myImage.png"
    img.SaveFile(fileName, wx.BITMAP_TYPE_PNG)
    print ('...saving as png!')

The above works for me on Linux.

Only the first time.

Unless wx 4.1 fixed it over 4.0.

Karsten

On my OpenSuse 15.1 python 3.6.x wxPython 4.0.7 I am able to over write the file many times without error. meaning I get a new time stamp when I save to myImage.png. Karsten_hilbert - maybe you are talking about something other than the code I posted???

Maybe the title of this post was misleading. What I was trying to “screenshot” was the screen that was displayed int the client window. Which should be as displayed in the following image: https://github.com/hasii2011/PyUt/wiki/pics/CaptureImage.png

Unfortunately the screen-related DCs (Paint, Client, Window, Screen) on OSX are essentially write-only, and have been for a number of years. (Ever since wxMac switched from QuickDraw to CoreGraphics, if I remember correctly.) That means that they can’t retrieve pixels from the screen buffer, they can only draw. The reason behind this is that the newer display tech in the OS controls all drawing from all apps so it can optimize it, so the application itself is actually separated from the display buffer by a few more layers.

A possible workaround might be to use applescript to control the OS or some native app to take a screenshot, and then load and manipulate that image as needed. ISTR seeing something like that being done, but I couldn’t find any details about it.

1 Like

Ok. Thanks for this information. I thought I had read something like this somewhere in a google search but could not find it. I’ll try some other mechanism

Hmm. That is sad. So there is no way to capture the contents of a client window in a bitmap other than to launch some external app, figure out the part of the screen to capture and then do that. This seems like a problem I should work on with the core wxPython team. How would I do that? Checkout wxpython, hack a bunch of code and try to get the team to merge the code back to the mainline? Someone, please advise?

Here is an interesting project that has some python code associated with it.

This now works under the just released wxpython 4.1

I love it when problems fix themselves. :smiling_face_with_three_hearts:

Hmm. I really don’t. I would like to know what got fixed. :thinking: