ponty / pyscreenshot

Python screenshot library, replacement for the Pillow ImageGrab module on Linux.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

pyscreenshot.grab() on Mac, box is additive and not absolute.

J-Moravec opened this issue · comments

On linux, the bounding box is absolute, i.e., bounding box (5, 5, 10, 10), it means: point (5,5) and point (10,10). However, on Mac, the bounding box is additive, i.e., it means point (5,5) and point (15, 15). Ugly fix could be:

    def grab_to_file(self, filename, bbox=None, dpi=72):
        # FIXME: Should query dpi from somewhere, e.g for retina displays
        region = self.CG.CGRectMake(bbox[1], bbox[2], bbox[3]-bbox[1], bbox[4]-bbox[2]) if bbox else self.CG.CGRectInfinite

        # Create screenshot as CGImage
        image = self.CG.CGWindowListCreateImage(region,
                                                self.CG.kCGWindowListOptionOnScreenOnly, self.CG.kCGNullWindowID,
self.CG.kCGWindowImageDefault)
commented

Could you send a pull request?
I don't have a Mac, so I can not test it.

No problem pulling request. However, I am not Mac user myself so I would have to ask the user who reported this issue to me.

Anyway, I have been looking at code a bit more and this is in the other Mac implementation:

    def grab_to_file(self, filename, bbox=None):
        command = 'screencapture -x '
        if filename.endswith('.jpeg'):
            command += ' -t jpg'
        elif filename.endswith('.tiff'):
            command += ' -t tiff'
        elif filename.endswith('.bmp'):
            command += ' -t bmp'
        elif filename.endswith('.gif'):
            command += ' -t gif'
        elif filename.endswith('.pdf'):
            command += ' -t pdf'
        if bbox:
            command += ' -R%s,%s,%s,%s ' % (
                bbox[0], bbox[1], bbox[2] - bbox[0], bbox[3] - bbox[1]) # see here?
        command += filename
EasyProcess(command).call()

See the bbox[0] things?

As the user who had reported the original problem to @J-Moravec , I would be happy to help in any way I can to do whatever debugging is necessary.

Please let me know what I can do that may be helpful!

@bknowles Ok, first thing we need to do is to determine which backend you are using on your mac to draw stuff. If I am reading code correctly, @ponty would correct me, you it is done in loader, so please run following code:

import pyscreenshot
loader = pyscreenshot.Loader()
print(loader.all_names) # prints names of all available backends on your system
print(loader.selected()) # prints selected backend

If this would show something like Quartz, we are home and we can apply and test the fix as written in my first post.

In that case, clone my version: https://github.com/J-Moravec/pyscreenshot, go to the main folder (so you could see another folder called pyscreenshot and run python and import it:

import pyscreenshot

If you are not sure if python imported this local and not globally installed version, well, you can modify e.g., __init__.py and somewhere at start (after log.debug, for example) write:

print("Hi asshole")

as I did (or some different message, I am just used to communicate like that with computers:P). Then, when you are importing pyscreenshot from this local folder, it would display this message and you can be sure that you are using local and not global version.

Now, once you do this, you can write code like:

im = pyscreenshot.grab((100,100,150,150))
im.save("test.png")

And if the fix did work, the size of image should be 50x50. If it did not worked, then size of image would be 150x150.

If you can reproduce the second behaviour in your globally installed pyscreenshot, it would be awesome.

Thanks.

Okay, running your code as shown above:

$ cat pytest.py
import pyscreenshot
loader = pyscreenshot.Loader()
print(loader.all_names) # prints names of all available backends on your system
print(loader.selected()) # prints selected backend

The result is:

$ python pytest.py 
['mac_quartz', 'mac_screencapture', 'wx', 'pygtk', 'pyqt', 'scrot', 'imagemagick']
<pyscreenshot.plugins.mac_quartz.MacQuartzWrapper object at 0x10b263cd0>

I'll try again with your version.

Trying this slightly modified code:

cat pytest.py
import pyscreenshot
loader = pyscreenshot.Loader()
print(loader.all_names) # prints names of all available backends on your system
print(loader.selected()) # prints selected backend
im = pyscreenshot.grab((100,100,150,150))
im.save("test.png")

I get the result:

$ python pytest.py 
['mac_quartz', 'mac_screencapture', 'wx', 'pygtk', 'pyqt', 'scrot', 'imagemagick', 'pyside']
<pyscreenshot.plugins.mac_quartz.MacQuartzWrapper object at 0x1082ae2d0>
Traceback (most recent call last):
  File "pytest.py", line 5, in <module>
    im = pyscreenshot.grab((100,100,150,150))
  File "/Users/brad/src/BGEE/pyscreenshot/pyscreenshot/__init__.py", line 58, in grab
    return _grab(to_file=False, childprocess=childprocess, backend=backend, bbox=bbox)
  File "/Users/brad/src/BGEE/pyscreenshot/pyscreenshot/__init__.py", line 37, in _grab
    return run_in_childprocess(_grab_simple, imcodec.codec, to_file, backend, bbox, filename)
  File "/Users/brad/src/BGEE/pyscreenshot/pyscreenshot/procutil.py", line 33, in run_in_childprocess
    raise e
NameError: global name 'box' is not defined

However, I'm not sure that I ran this correctly. I cloned your repo into my ~/src/BGEE/ directory, and then did a cd pyscreenshot before running the above command.

You ran code well. The problem is because I can't for some reason write code, it should be bbox not just box. Corrected, can you try it again?

Anyway, I think that we are quite sure there. The plugin that is picked is mac_quartz, so if there is any error (with window size), it is really in this file. And mathematically, proposed fix explain current behaviour.

So I will just ask you to run it again (now, with corrected code) and we can commit.

I pulled a new clone and re-ran the test, but then I got a crash -- see http://imgur.com/a/1uQP0. I think this is the same problem we ran into before, but I don't remember what the solution was.

Ahh, yes the childprocess = True problem. To get around this issue, I made the following temporary change:

$ git diff
diff --git a/pyscreenshot/__init__.py b/pyscreenshot/__init__.py
index 4a77fcf..3d7a530 100644
--- a/pyscreenshot/__init__.py
+++ b/pyscreenshot/__init__.py
@@ -18,7 +18,7 @@ def childprocess_default_value():
     Therefore the default is False for childprocess
     if the program was started inside IDLE. 
     '''
-    return not is_inside_idle()
+    return False
 
 def _grab_simple(to_file, backend=None, bbox=None, filename=None):
     loader = Loader()

I was then able to get the test program to execute. The resulting PNG is 100x100 pixels, which I believe is because I'm using a Retina screen on my MacBook Pro -- see http://imgur.com/a/56aMm.

So, you are NOT running script outside of IDLE and still getting this error? If so, we might require another fix. However, the weird thing is:

class MacQuartzWrapper(object):
    name = 'mac_quartz'
    childprocess = False # already set to False

We might have to investigate more what is really happening there. Give me more time.

The Retina display might explain why Mac has coordinates as floats and not integers in this other library.

So, you are NOT running script outside of IDLE and still getting this error?

I believe that is correct.

We might have to investigate more what is really happening there. Give me more time.

Yeah, I'm not sure how that is happening. Maybe somehow is_inside_idle() is not returning the expected value on macOS?

The Retina display might explain why Mac has coordinates as floats and not integers in this other library.

That totally makes sense. There are multiple values for screen scaling, some of which would result in virtual pixels that are 1.5 times as wide or tall as the physical pixels of the display. Default is a straight 2x, but you can go larger and smaller than that, if you so choose.

Apple doesn't make it easy for the user to find out exactly what the scaling is, just options like "Default", "More Space", "Larger Text", and then selections that are halfway between some of the above. But, it does handle the process of converting to and from physical pixels when you request a screen grab, it's just that the image may end up being larger and higher resolution than you would have otherwise expected.

I'm sure that HighDPI screens under Windows and other OSes might likewise behave in ways that are not necessarily expected.

commented

If you have any problem with IDLE then please comment in #38.

I added recently improvements but I could not test it on MacOS.

commented

I implemented your suggestion.
Please test it if you can.

import pyscreenshot as ImageGrab
import logging

logging.basicConfig(level=logging.DEBUG)

childprocess = True
bbox = (10, 10, 510, 510)  # X1,Y1,X2,Y2
backend = 'mac_quartz'

im = ImageGrab.grab(bbox=bbox, backend=backend, childprocess=childprocess)
print im.size
# im.show()

Any updates on this issue?
I could not get the imageGrab working for on 15 inch MacBook Pro...

commented

Pillow supports MacOS, so there is no need for pyscreenshot on this platform.

https://pillow.readthedocs.io/en/3.0.x/reference/ImageGrab.html
"ImageGrab Module (OS X and Windows only)"