Add scale option to ImageGrab.grab function
https://github.com/python-pillow/Pillow/blob/33eb16bb39df3ec9b3a728d46c43790b2af817c7/src/PIL/ImageGrab.py#L54 produces a low quality image if bbox is given. The reason is that the screenshot for a region 100x200 is a 200x400 image on a Macbook Pro which will scale the the region by 2. After the resizing, the image quality is reduced.
If no bbox is provide, grab function produces a 3024x1964 image for the whole screen instead of a 1512x982 image. The resulted image does not have its quality reduced.
So the API has an inconsistent behavior. If bbox is not given, the resulted screenshot image has a good quality. But if bbox is given, the resulted screenshot image has a low quality.
So I suggest to add option to control the scaling like what mss package has done. mss has IMAGE_OPTIONS to control the scaling.See https://github.com/BoboTiG/python-mss/blob/main/CHANGELOG.md#1010-2025-08-16 for details.
What are your OS, Python and Pillow versions?
- OS: macOS 14.3.1
- Python: 3.10.18
- Pillow: 11.3.0
python3 -m PIL.report produces:
Pillow 11.3.0
Python 3.10.18 (main, Jun 5 2025, 08:37:47) [Clang 14.0.6 ]
--------------------------------------------------------------------
Python executable is /Users/jing/miniconda3/envs/m-agent/bin/python
System Python files loaded from /Users/jing/miniconda3/envs/m-agent
--------------------------------------------------------------------
Python Pillow modules loaded from /Users/jing/miniconda3/envs/m-agent/lib/python3.10/site-packages/PIL
Binary Pillow modules loaded from /Users/jing/miniconda3/envs/m-agent/lib/python3.10/site-packages/PIL
--------------------------------------------------------------------
--- PIL CORE support ok, compiled for 11.3.0
--- TKINTER support ok, loaded 8.6
--- FREETYPE2 support ok, loaded 2.13.3
--- LITTLECMS2 support ok, loaded 2.17
--- WEBP support ok, loaded 1.5.0
--- AVIF support ok, loaded 1.3.0
--- JPEG support ok, compiled for libjpeg-turbo 3.1.1
--- OPENJPEG (JPEG2000) support ok, loaded 2.5.3
--- ZLIB (PNG/ZIP) support ok, loaded 1.3.1.zlib-ng, compiled for zlib-ng 2.2.4
--- LIBTIFF support ok, loaded 4.7.0
*** RAQM (Bidirectional Text) support not installed
*** LIBIMAGEQUANT (Quantization method) support not installed
--- XCB (X protocol) support ok
--------------------------------------------------------------------
Hi. What you're describing is documented at https://pillow.readthedocs.io/en/stable/reference/ImageGrab.html#PIL.ImageGrab.grab
If the bounding box is omitted, the entire screen is copied, and on macOS, it will be at 2x if on a Retina screen.
We've had discussions about this before - https://github.com/python-pillow/Pillow/issues/7677#issuecomment-1874620218
If you consider this from the perspective of the rest of Pillow, such as
im.crop(box=...), I think it is more expected to have the rectangle co-ordinates produce an image that has dimensions(x2 - x1, y2 - y1).
When discussing this in https://github.com/python-pillow/Pillow/issues/6144, Apple's documentation was found to say
There are very few, if any, situations for which you need to use device coordinates directly.
I expect you're aware that you could just call ImageGrab.grab() without a bbox and then crop() it yourself?
the resulted screenshot image has a low quality.
I'm not sure if by 'low quality' you simply mean that it is scaled down, or if you think that our resize operation doesn't scale an image down well. If you think that our resize operation produces a low quality result, that is a separate discussion to the ImageGrab functionality.
I expect you're aware that you could just call ImageGrab.grab() without a bbox and then crop() it yourself?
I know that this works. But it wastes resources to do the whole screen capture.
I'm not sure if by 'low quality' you simply mean that it is scaled down, or if you think that our resize operation doesn't scale an image down well. If you think that our resize operation produces a low quality result, that is a separate discussion to the ImageGrab functionality.
'low quality' means a low resolution. For example, I pass a bbox (0, 0, 100, 100). ImageGrab.grab with this bbox would produce a 200x200 image on Retina display. But resize function reduces the image resolution to 100x100. The point is that ImageGrab.grab calls screencapture to produce what I want. But it converts it into some unwanted thing.
I've created https://github.com/python-pillow/Pillow/pull/9266 to use retina size by default, and to allow users to scale it down to 1x with ImageGrab.grab(scale_down=True).