Giter Club home page Giter Club logo

d3dshot's Introduction

D3DShot

D3DShot is a pure Python implementation of the Windows Desktop Duplication API. It leverages DXGI and Direct3D system libraries to enable extremely fast and robust screen capture functionality for your Python scripts and applications on Windows.

D3DShot:

  • Is by far the fastest way to capture the screen with Python on Windows 8.1+
  • Is very easy to use. If you can remember 10-ish methods, you know the entire thing.
  • Covers all common scenarios and use cases:
    • Screenshot to memory
    • Screenshot to disk
    • Screenshot to memory buffer every X seconds (threaded; non-blocking)
    • Screenshot to disk every X seconds (threaded; non-blocking)
    • High-speed capture to memory buffer (threaded; non-blocking)
  • Captures to PIL Images out of the box. Gracefully adds output options if NumPy or PyTorch can be found.
  • Detects displays in just about any configuration: Single monitor, multiple monitors on one adapter, multiple monitors on multiple adapters.
  • Handles display rotation and scaling for you
  • Supports capturing specific regions of the screen
  • Is robust and very stable. You can run it for hours / days without performance degradation
  • Is even able to capture DirectX 11 / 12 exclusive fullscreen applications and games!

TL;DR Quick Code Samples

Screenshot to Memory

import d3dshot

d = d3dshot.create()
d.screenshot()
Out[1]: <PIL.Image.Image image mode=RGB size=2560x1440 at 0x1AA7ECB5C88>

Screenshot to Disk

import d3dshot

d = d3dshot.create()
d.screenshot_to_disk()
Out[1]: './1554298682.5632973.png'

Screen Capture for 5 Seconds and Grab the Latest Frame

import d3dshot
import time

d = d3dshot.create()

d.capture()
time.sleep(5)  # Capture is non-blocking so we wait explicitely
d.stop()

d.get_latest_frame()
Out[1]: <PIL.Image.Image image mode=RGB size=2560x1440 at 0x1AA044BCF60>

Screen Capture the Second Monitor as NumPy Arrays for 3 Seconds and Grab the 4 Latest Frames as a Stack

import d3dshot
import time

d = d3dshot.create(capture_output="numpy")

d.display = d.displays[1]

d.capture()
time.sleep(3)  # Capture is non-blocking so we wait explicitely
d.stop()

frame_stack = d.get_frame_stack((0, 1, 2, 3), stack_dimension="last")
frame_stack.shape
Out[1]: (1080, 1920, 3, 4)

This is barely scratching the surface... Keep reading!

Requirements

  • Windows 8.1+ (64-bit)
  • Python 3.6+ (64-bit)

Installation

pip install d3dshot

D3DShot leverages DLLs that are already available on your system so the dependencies are very light. Namely:

  • comtypes: Internal use. To preserve developer sanity while working with COM interfaces.
  • Pillow: Default Capture Output. Also used to save to disk as PNG and JPG.

These dependencies will automatically be installed alongside D3DShot; No need to worry about them!

Extra Step: Laptop Users

Windows has a quirk when using Desktop Duplication on hybrid-GPU systems. Please see the wiki article before attempting to use D3DShot on your system.

Concepts

Capture Outputs

The desired Capture Output is defined when creating a D3DShot instance. It defines the type of all captured images. By default, all captures will return PIL.Image objects. This is a good option if you mostly intend to take screenshots.

# Captures will be PIL.Image in RGB mode
d = d3dshot.create()
d = d3dshot.create(capture_output="pil")

D3DShot is however quite flexible! As your environment meets certain optional sets of requirements, more options become available.

If NumPy is available

# Captures will be np.ndarray of dtype uint8 with values in range (0, 255)
d = d3dshot.create(capture_output="numpy")

# Captures will be np.ndarray of dtype float64 with normalized values in range (0.0, 1.0)
d = d3dshot.create(capture_output="numpy_float")  

If NumPy and PyTorch are available

# Captures will be torch.Tensor of dtype uint8 with values in range (0, 255)
d = d3dshot.create(capture_output="pytorch")

# Captures will be torch.Tensor of dtype float64 with normalized values in range (0.0, 1.0)
d = d3dshot.create(capture_output="pytorch_float")

If NumPy and PyTorch are available + CUDA is installed and torch.cuda.is_available()

# Captures will be torch.Tensor of dtype uint8 with values in range (0, 255) on device cuda:0
d = d3dshot.create(capture_output="pytorch_gpu")

# Captures will be torch.Tensor of dtype float64 with normalized values in range (0.0, 1.0) on device cuda:0
d = d3dshot.create(capture_output="pytorch_float_gpu")

Trying to use a Capture Output for which your environment does not meet the requirements will result in an error.

Singleton

Windows only allows 1 instance of Desktop Duplication per process. To make sure we fall in line with that limitation to avoid issues, the D3DShot class acts as a singleton. Any subsequent calls to d3dshot.create() will always return the existing instance.

d = d3dshot.create(capture_output="numpy")

# Attempting to create a second instance
d2 = d3dshot.create(capture_output="pil")
# Only 1 instance of D3DShot is allowed per process! Returning the existing instance...

# Capture output remains 'numpy'
d2.capture_output.backend
# Out[1]: <d3dshot.capture_outputs.numpy_capture_output.NumpyCaptureOutput at 0x2672be3b8e0>

d == d2
# Out[2]: True

Frame Buffer

When you create a D3DShot instance, a frame buffer is also initialized. It is meant as a thread-safe, first-in, first-out way to hold a certain quantity of captures and is implemented as a collections.deque.

By default, the size of the frame buffer is set to 60. You can customize it when creating your D3DShot object.

d = d3dshot.create(frame_buffer_size=100)

Be mindful of RAM usage with larger values; You will be dealing with uncompressed images which use up to 100 MB each depending on the resolution.

The frame buffer can be accessed directly with d.frame_buffer but the usage of the utility methods instead is recommended.

The buffer is used by the following methods:

  • d.capture()
  • d.screenshot_every()

It is always automatically cleared before starting one of these operations.

Displays

When you create a D3DShot instance, your available displays will automatically be detected along with all their relevant properties.

d.displays
Out[1]: 
[<Display name=BenQ XL2730Z (DisplayPort) adapter=NVIDIA GeForce GTX 1080 Ti resolution=2560x1440 rotation=0 scale_factor=1.0 primary=True>,
 <Display name=BenQ XL2430T (HDMI) adapter=Intel(R) UHD Graphics 630 resolution=1920x1080 rotation=0 scale_factor=1.0 primary=False>]

By default, your primary display will be selected. At all times you can verify which display is set to be used for capture.

d.display
Out[1]: <Display name=BenQ XL2730Z (DisplayPort) adapter=NVIDIA GeForce GTX 1080 Ti resolution=2560x1440 rotation=0 scale_factor=1.0 primary=True>

Selecting another display for capture is as simple as setting d.display to another value from d.displays

d.display = d.displays[1]
d.display
Out[1]: <Display name=BenQ XL2430T (HDMI) adapter=Intel(R) UHD Graphics 630 resolution=1080x1920 rotation=90 scale_factor=1.0 primary=False>

Display rotation and scaling is detected and handled for you by D3DShot:

  • Captures on rotated displays will always be in the correct orientation (i.e. matching what you see on your physical displays)
  • Captures on scaled displays will always be in full, non-scaled resolution (e.g. 1280x720 at 200% scaling will yield 2560x1440 captures)

Regions

All capture methods (screenshots included) accept an optional region kwarg. The expected value is a 4-length tuple of integers that is to be structured like this:

(left, top, right, bottom)  # values represent pixels

For example, if you want to only capture a 200px by 200px region offset by 100px from both the left and top, you would do:

d.screenshot(region=(100, 100, 300, 300))

If you are capturing a scaled display, the region will be computed against the full, non-scaled resolution.

If you go through the source code, you will notice that the region cropping happens after a full display capture. That might seem sub-optimal but testing has revealed that copying a region of the GPU D3D11Texture2D to the destination CPU D3D11Texture2D using CopySubresourceRegion is only faster when the region is very small. In fact, it doesn't take long for larger regions to actually start becoming slower than the full display capture using this method. To make things worse, it adds a lot of complexity by having the surface pitch not match the buffer size and treating rotated displays differently. It was therefore decided that it made more sense to stick to CopyResource in all cases and crop after the fact.

Usage

Create a D3DShot instance

import d3dshot

d = d3dshot.create()

create accepts 2 optional kwargs:

  • capture_output: Which capture output to use. See the Capture Outputs section under Concepts
  • frame_buffer_size: The maximum size the frame buffer can grow to. See the Frame Buffer section under Concepts

Do NOT import the D3DShot class directly and attempt to initialize it yourself! The create helper function initializes and validates a bunch of things for you behind the scenes.

Once you have a D3DShot instance in scope, we can start doing stuff with it!

List the detected displays

d.displays

Select a display for capture

Your primary display is selected by default but if you have a multi-monitor setup, you can select another entry in d.displays

d.display = d.displays[1]

Take a screenshot

d.screenshot()

screenshot accepts 1 optional kwarg:

  • region: A region tuple. See the Regions section under Concepts

Returns: A screenshot with a format that matches the capture output you selected when creating your D3DShot object

Take a screenshot and save it to disk

d.screenshot_to_disk()

screenshot_to_disk accepts 3 optional kwargs:

  • directory: The path / directory where to write the file. If omitted, the working directory of the program will be used
  • file_name: The file name to use. Permitted extensions are: .png, .jpg. If omitted, the file name will be <time.time()>.png
  • region: A region tuple. See the Regions section under Concepts

Returns: A string representing the full path to the saved image file

Take a screenshot every X seconds

d.screenshot_every(X)  # Where X is a number representing seconds

This operation is threaded and non-blocking. It will keep running until d.stop() is called. Captures are pushed to the frame buffer.

screenshot_every accepts 1 optional kwarg:

  • region: A region tuple. See the Regions section under Concepts

Returns: A boolean indicating whether or not the capture thread was started

Take a screenshot every X seconds and save it to disk

d.screenshot_to_disk_every(X)  # Where X is a number representing seconds

This operation is threaded and non-blocking. It will keep running until d.stop() is called.

screenshot_to_disk_every accepts 2 optional kwargs:

  • directory: The path / directory where to write the file. If omitted, the working directory of the program will be used
  • region: A region tuple. See the Regions section under Concepts

Returns: A boolean indicating whether or not the capture thread was started

Start a high-speed screen capture

d.capture()

This operation is threaded and non-blocking. It will keep running until d.stop() is called. Captures are pushed to the frame buffer.

capture accepts 2 optional kwargs:

  • target_fps: How many captures per second to aim for. The effective capture rate will go under if the system can't keep up but it will never go over this target. It is recommended to set this to a reasonable value for your use case in order not to waste system resources. Default is set to 60.
  • region: A region tuple. See the Regions section under Concepts

Returns: A boolean indicating whether or not the capture thread was started

Grab the latest frame from the buffer

d.get_latest_frame()

Returns: A frame with a format that matches the capture output you selected when creating your D3DShot object

Grab a specific frame from the buffer

d.get_frame(X)  # Where X is the index of the desired frame. Needs to be < len(d.frame_buffer)

Returns: A frame with a format that matches the capture output you selected when creating your D3DShot object

Grab specific frames from the buffer

d.get_frames([X, Y, Z, ...])  # Where X, Y, Z are valid indices to desired frames

Returns: A list of frames with a format that matches the capture output you selected when creating your D3DShot object

Grab specific frames from the buffer as a stack

d.get_frame_stack([X, Y, Z, ...], stack_dimension="first|last")  # Where X, Y, Z are valid indices to desired frames

Only has an effect on NumPy and PyTorch capture outputs.

get_frame_stack accepts 1 optional kwarg:

  • stack_dimension: One of first, last. Which axis / dimension to perform the stack on

Returns: A single array stacked on the specified dimension with a format that matches the capture output you selected when creating your D3DShot object. If the capture output is not stackable, returns a list of frames.

Dump the frame buffer to disk

The files will be named according to this convention: <frame buffer index>.png

d.frame_buffer_to_disk()

frame_buffer_to_disk accepts 1 optional kwarg:

  • directory: The path / directory where to write the files. If omitted, the working directory of the program will be used

Returns: None

Performance

Measuring the exact performance of the Windows Desktop Duplication API proves to be a little complicated because it will only return new texture data if the contents of the screen has changed. This is optimal for performance but it makes it difficult to express in terms of frames per second, the measurement people tend to expect for benchmarks. Ultimately the solution ended up being to run a high FPS video game on the display to capture to make sure the screen contents is different at all times while benchmarking.

As always, remember that benchmarks are inherently flawed and highly depend on your individual hardware configuration and other circumstances. Use the numbers below as a relative indication of what to expect from D3DShot, not as some sort of absolute truth.

2560x1440 on NVIDIA GTX 1080 Ti 1920x1080 on Intel UHD Graphics 630 1080x1920 (vertical) on Intel UHD Graphics 630
"pil" 29.717 FPS 47.75 FPS 35.95 FPS
"numpy" 57.667 FPS 58.1 FPS 58.033 FPS
"numpy_float" 18.783 FPS 29.05 FPS 27.517 FPS
"pytorch" 57.867 FPS 58.1 FPS 34.817 FPS
"pytorch_float" 18.767 FPS 28.367 FPS 27.017 FPS
"pytorch_gpu" 27.333 FPS 35.767 FPS 34.8 FPS
"pytorch_float_gpu" 27.267 FPS 37.383 FPS 35.033 FPS

The absolute fastest capture outputs appear to be "numpy" and unrotated "pytorch"; all averaging around 58 FPS. In Python land, this is FAST!

How is the "numpy" capture output performance that good?

NumPy arrays have a ctypes interface that can give you their raw memory address (X.ctypes.data). If you have the memory address and size of another byte buffer, which is what we end up with by processing what returns from the Desktop Duplication API, you can use ctypes.memmove to copy that byte buffer directly to the NumPy structure, effectively bypassing as much Python as possible.

In practice it ends up looking like this:

ctypes.memmove(np.empty((size,), dtype=np.uint8).ctypes.data, pointer, size)

This low-level operation is extremely fast, leaving everything else that would normally compete with NumPy in the dust.

Why is the "pytorch" capture output slower on rotated displays?

Don't tell anyone but the reason it can compete with NumPy in the first place is only because... it is generated from a NumPy array built from the method above! If you sniff around the code, you will indeed find torch.from_numpy() scattered around. This pretty much matches the speed of the "numpy" capture output 1:1, except when dealing with a rotated display. Display rotation is handled by np.rot90() calls which yields negative strides on that array. Negative strides are understood and perform well under NumPy but are still unsupported in PyTorch at the time of writing. To address this, an additional copy operation is needed to bring it back to a contiguous array which imposes a performance penalty.

Why is the "pil" capture output, being the default, not the fastest?

PIL has no ctypes interface like NumPy so a bytearray needs to be read into Python first and then fed to PIL.Image.frombytes(). This is still fast in Python terms, but it just cannot match the speed of the low-level NumPy method.

It remains the default capture output because:

  1. PIL Image objects tend to be familiar to Python users
  2. It's a way lighter / simpler dependency for a library compared to NumPy or PyTorch

Why are the float versions of capture outputs slower?

The data of the Direct3D textures made accessible by the Desktop Duplication API is formatted as bytes. To represent this data as normalized floats instead, a type cast and element-wise division needs to be performed on the array holding those bytes. This imposes a major performance penalty. Interestingly, you can see this performance penalty mitigated on GPU PyTorch tensors since the element-wise division can be massively parallelized on the device.

Crafted with ❤ by Serpent.AI 🐍
Twitter - Twitch

d3dshot's People

Contributors

eh8 avatar nbrochu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

d3dshot's Issues

pip install d3dshot isn't working

I follow https://pypi.org/project/d3dshot/

My python version is 3.10

I install Pillow package using pip install Pillow
then I run pip install d3dshot on Windows PowerShell and get this error :

PS C:\Users\Yanothai Chaitawat> pip install d3dshot
Collecting d3dshot
  Using cached D3DShot-0.1.5-py3-none-any.whl (24 kB)
Collecting comtypes<1.2.0,>=1.1.7
  Using cached comtypes-1.1.11-py2.py3-none-any.whl (167 kB)
Collecting pillow<7.2.0,>=7.1.2
  Using cached Pillow-7.1.2.tar.gz (38.9 MB)
  Preparing metadata (setup.py) ... done
Using legacy 'setup.py install' for pillow, since package 'wheel' is not installed.
Installing collected packages: pillow, comtypes, d3dshot
  Attempting uninstall: pillow
    Found existing installation: Pillow 9.1.0
    Uninstalling Pillow-9.1.0:
      Successfully uninstalled Pillow-9.1.0
    Running setup.py install for pillow ... error
    ERROR: Command errored out with exit status 1:
     command: 'C:\Users\Yanothai Chaitawat\AppData\Local\Programs\Python\Python310\python.exe' -u -c 'import io, os, sys, setuptools, tokenize; sys.argv[0] = '"'"'C:\\Users\\Yanothai Chaitawat\\AppData\\Local\\Temp\\pip-install-8ciz7jnr\\pillow_a2725d3477104ef19c3fdc565e33e203\\setup.py'"'"'; __file__='"'"'C:\\Users\\Yanothai Chaitawat\\AppData\\Local\\Temp\\pip-install-8ciz7jnr\\pillow_a2725d3477104ef19c3fdc565e33e203\\setup.py'"'"';f = getattr(tokenize, '"'"'open'"'"', open)(__file__) if os.path.exists(__file__) else io.StringIO('"'"'from setuptools import setup; setup()'"'"');code = f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' install --record 'C:\Users\Yanothai Chaitawat\AppData\Local\Temp\pip-record-_hls1_ye\install-record.txt' --single-version-externally-managed --compile --install-headers 'C:\Users\Yanothai Chaitawat\AppData\Local\Programs\Python\Python310\Include\pillow'
         cwd: C:\Users\Yanothai Chaitawat\AppData\Local\Temp\pip-install-8ciz7jnr\pillow_a2725d3477104ef19c3fdc565e33e203\
    Complete output (175 lines):
    C:\Users\Yanothai Chaitawat\AppData\Local\Temp\pip-install-8ciz7jnr\pillow_a2725d3477104ef19c3fdc565e33e203\setup.py:42: RuntimeWarning: Pillow 7.1.2 does not support Python 3.10 and does not provide prebuilt Windows binaries. We do not recommend building from source on Windows.
      warnings.warn(
    running install
    C:\Users\Yanothai Chaitawat\AppData\Local\Programs\Python\Python310\lib\site-packages\setuptools\command\install.py:34: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools.
      warnings.warn(
    running build
    running build_py
    creating build
    creating build\lib.win-amd64-3.10
    creating build\lib.win-amd64-3.10\PIL
    copying src\PIL\BdfFontFile.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\BlpImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\BmpImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\BufrStubImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\ContainerIO.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\CurImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\DcxImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\DdsImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\EpsImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\ExifTags.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\features.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\FitsStubImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\FliImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\FontFile.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\FpxImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\FtexImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\GbrImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\GdImageFile.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\GifImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\GimpGradientFile.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\GimpPaletteFile.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\GribStubImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\Hdf5StubImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\IcnsImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\IcoImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\Image.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\ImageChops.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\ImageCms.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\ImageColor.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\ImageDraw.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\ImageDraw2.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\ImageEnhance.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\ImageFile.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\ImageFilter.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\ImageFont.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\ImageGrab.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\ImageMath.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\ImageMode.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\ImageMorph.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\ImageOps.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\ImagePalette.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\ImagePath.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\ImageQt.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\ImageSequence.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\ImageShow.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\ImageStat.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\ImageTk.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\ImageTransform.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\ImageWin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\ImImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\ImtImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\IptcImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\Jpeg2KImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\JpegImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\JpegPresets.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\McIdasImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\MicImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\MpegImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\MpoImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\MspImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\PaletteFile.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\PalmImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\PcdImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\PcfFontFile.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\PcxImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\PdfImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\PdfParser.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\PixarImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\PngImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\PpmImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\PsdImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\PSDraw.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\PyAccess.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\SgiImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\SpiderImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\SunImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\TarIO.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\TgaImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\TiffImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\TiffTags.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\WalImageFile.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\WebPImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\WmfImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\XbmImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\XpmImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\XVThumbImagePlugin.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\_binary.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\_tkinter_finder.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\_util.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\_version.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\__init__.py -> build\lib.win-amd64-3.10\PIL
    copying src\PIL\__main__.py -> build\lib.win-amd64-3.10\PIL
    running egg_info
    warning: no files found matching '*.c'
    warning: no files found matching '*.h'
    warning: no files found matching '*.sh'
    warning: no previously-included files found matching '.appveyor.yml'
    warning: no previously-included files found matching '.coveragerc'
    warning: no previously-included files found matching '.editorconfig'
    warning: no previously-included files found matching '.readthedocs.yml'
    warning: no previously-included files found matching 'azure-pipelines.yml'
    warning: no previously-included files found matching 'codecov.yml'
    warning: no previously-included files matching '.git*' found anywhere in distribution
    warning: no previously-included files matching '*.pyc' found anywhere in distribution
    warning: no previously-included files matching '*.so' found anywhere in distribution
    no previously-included directories found matching '.azure-pipelines'
    no previously-included directories found matching '.ci'
    writing manifest file 'src\Pillow.egg-info\SOURCES.txt'
    running build_ext


    The headers or library files could not be found for zlib,
    a required dependency when compiling Pillow from source.

    Please see the install instructions at:
       https://pillow.readthedocs.io/en/latest/installation.html

    Traceback (most recent call last):
      File "C:\Users\Yanothai Chaitawat\AppData\Local\Temp\pip-install-8ciz7jnr\pillow_a2725d3477104ef19c3fdc565e33e203\setup.py", line 860, in <module>
        setup(
      File "C:\Users\Yanothai Chaitawat\AppData\Local\Programs\Python\Python310\lib\site-packages\setuptools\__init__.py", line 155, in setup
        return distutils.core.setup(**attrs)
      File "C:\Users\Yanothai Chaitawat\AppData\Local\Programs\Python\Python310\lib\site-packages\setuptools\_distutils\core.py", line 148, in setup
        return run_commands(dist)
      File "C:\Users\Yanothai Chaitawat\AppData\Local\Programs\Python\Python310\lib\site-packages\setuptools\_distutils\core.py", line 163, in run_commands
        dist.run_commands()
      File "C:\Users\Yanothai Chaitawat\AppData\Local\Programs\Python\Python310\lib\site-packages\setuptools\_distutils\dist.py", line 967, in run_commands
        self.run_command(cmd)
      File "C:\Users\Yanothai Chaitawat\AppData\Local\Programs\Python\Python310\lib\site-packages\setuptools\_distutils\dist.py", line 986, in run_command
        cmd_obj.run()
      File "C:\Users\Yanothai Chaitawat\AppData\Local\Programs\Python\Python310\lib\site-packages\setuptools\command\install.py", line 68, in run
        return orig.install.run(self)
      File "C:\Users\Yanothai Chaitawat\AppData\Local\Programs\Python\Python310\lib\site-packages\setuptools\_distutils\command\install.py", line 662, in run
        self.run_command('build')
      File "C:\Users\Yanothai Chaitawat\AppData\Local\Programs\Python\Python310\lib\site-packages\setuptools\_distutils\cmd.py", line 313, in run_command
        self.distribution.run_command(command)
      File "C:\Users\Yanothai Chaitawat\AppData\Local\Programs\Python\Python310\lib\site-packages\setuptools\_distutils\dist.py", line 986, in run_command
        cmd_obj.run()
      File "C:\Users\Yanothai Chaitawat\AppData\Local\Programs\Python\Python310\lib\site-packages\setuptools\_distutils\command\build.py", line 135, in run
        self.run_command(cmd_name)
      File "C:\Users\Yanothai Chaitawat\AppData\Local\Programs\Python\Python310\lib\site-packages\setuptools\_distutils\cmd.py", line 313, in run_command
        self.distribution.run_command(command)
      File "C:\Users\Yanothai Chaitawat\AppData\Local\Programs\Python\Python310\lib\site-packages\setuptools\_distutils\dist.py", line 986, in run_command
        cmd_obj.run()
      File "C:\Users\Yanothai Chaitawat\AppData\Local\Programs\Python\Python310\lib\site-packages\setuptools\_distutils\command\build_ext.py", line 339, in run
        self.build_extensions()
      File "C:\Users\Yanothai Chaitawat\AppData\Local\Temp\pip-install-8ciz7jnr\pillow_a2725d3477104ef19c3fdc565e33e203\setup.py", line 694, in build_extensions
        raise RequiredDependencyException(f)
    __main__.RequiredDependencyException: zlib

    During handling of the above exception, another exception occurred:

    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "C:\Users\Yanothai Chaitawat\AppData\Local\Temp\pip-install-8ciz7jnr\pillow_a2725d3477104ef19c3fdc565e33e203\setup.py", line 914, in <module>
        raise RequiredDependencyException(msg)
    __main__.RequiredDependencyException:

    The headers or library files could not be found for zlib,
    a required dependency when compiling Pillow from source.

    Please see the install instructions at:
       https://pillow.readthedocs.io/en/latest/installation.html


    ----------------------------------------
  Rolling back uninstall of Pillow
  Moving to c:\users\yanothai chaitawat\appdata\local\programs\python\python310\lib\site-packages\pil\
   from C:\Users\Yanothai Chaitawat\AppData\Local\Programs\Python\Python310\Lib\site-packages\~il
  Moving to c:\users\yanothai chaitawat\appdata\local\programs\python\python310\lib\site-packages\pillow-9.1.0.dist-info\
   from C:\Users\Yanothai Chaitawat\AppData\Local\Programs\Python\Python310\Lib\site-packages\~illow-9.1.0.dist-info
ERROR: Command errored out with exit status 1: 'C:\Users\Yanothai Chaitawat\AppData\Local\Programs\Python\Python310\python.exe' -u -c 'import io, os, sys, setuptools, tokenize; sys.argv[0] = '"'"'C:\\Users\\Yanothai Chaitawat\\AppData\\Local\\Temp\\pip-install-8ciz7jnr\\pillow_a2725d3477104ef19c3fdc565e33e203\\setup.py'"'"'; __file__='"'"'C:\\Users\\Yanothai Chaitawat\\AppData\\Local\\Temp\\pip-install-8ciz7jnr\\pillow_a2725d3477104ef19c3fdc565e33e203\\setup.py'"'"';f = getattr(tokenize, '"'"'open'"'"', open)(__file__) if os.path.exists(__file__) else io.StringIO('"'"'from setuptools import setup; setup()'"'"');code = f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' install --record 'C:\Users\Yanothai Chaitawat\AppData\Local\Temp\pip-record-_hls1_ye\install-record.txt' --single-version-externally-managed --compile --install-headers 'C:\Users\Yanothai Chaitawat\AppData\Local\Programs\Python\Python310\Include\pillow' Check the logs for full command output.
WARNING: You are using pip version 21.3.1; however, version 22.0.4 is available.
You should consider upgrading via the 'C:\Users\Yanothai Chaitawat\AppData\Local\Programs\Python\Python310\python.exe -m pip install --upgrade pip' command.

And during d3dshot even tried to uninstall the Pillow package.
I don't see why this module should do that.

COMError with pytorch

hi,thanks for your great lib. when i use it in a conda environment installed pytorch_gpu,it doensn't work, error occurs
Traceback (most recent call last): File "C:/CenterNet/src/d3d_demo.py", line 10, in <module> d = d3dshot.create(capture_output="numpy") #capture_output="numpy" File "C:\Users\yao\Anaconda3\envs\CenterNet\lib\site-packages\d3dshot\__init__.py", line 70, in create pytorch_gpu_is_available=pytorch_gpu_is_available File "C:\Users\yao\Anaconda3\envs\CenterNet\lib\site-packages\d3dshot\d3dshot.py", line 24, in __init__ self.detect_displays() File "C:\Users\yao\Anaconda3\envs\CenterNet\lib\site-packages\d3dshot\d3dshot.py", line 194, in detect_displays self.displays = Display.discover_displays() File "C:\Users\yao\Anaconda3\envs\CenterNet\lib\site-packages\d3dshot\display.py", line 119, in discover_displays dxgi_adapter=dxgi_adapter File "C:\Users\yao\Anaconda3\envs\CenterNet\lib\site-packages\d3dshot\display.py", line 40, in __init__ self.dxgi_output_duplication = self._initialize_dxgi_output_duplication() File "C:\Users\yao\Anaconda3\envs\CenterNet\lib\site-packages\d3dshot\display.py", line 66, in _initialize_dxgi_output_duplication return d3dshot.dll.dxgi.initialize_dxgi_output_duplication(self.dxgi_output, self.d3d_device) File "C:\Users\yao\Anaconda3\envs\CenterNet\lib\site-packages\d3dshot\dll\dxgi.py", line 257, in initialize_dxgi_output_duplication dxgi_output.DuplicateOutput(d3d_device, ctypes.byref(dxgi_output_duplication)) _ctypes.COMError: (-2005270524, 'The specified device interface or feature level is not supported on this system。', (None, None, None, 0, None))
when I uninstalled pytorch, it can capture frames well,(but first run frame is full of noise).
I wanna use d3dshot and pytorch to make a aimbot ,may help me solve this problems,thank u very much!

monitor resolution not detected properly

I'm using a 2560x1440p monitor with no scaling set. I double checked in nvidia control panel that I'm running native 2560x1440p resolution. When I call d.devices, I get this:

<Display name=Generic PnP Monitor adapter=NVIDIA GeForce RTX 2070 SUPER resolution=2048x1152 rotation=0 scale_factor=1.25 primary=True>

According to this, my resolution is only 2048x1152 with a scale factor of 1.25. I believe this is causing an issue for me, because I am not able to capture the entire output of my monitor, even when I set region=(0, 0, 2559, 1439).

Any ideas?

Feature to wait for any change

I needed to detect if a specific region has changed. So I've built a loop waiting for any changes in a specific region, but it used too much CPU. I've found out my program was running very fast, around 180 fps.

If there was a method to wait for any potention change in the region, it would help to reduce CPU usage drastically. I think it's possible to do that cheaply using IDXGIOutputDuplication::GetFrameDirtyRects(). (Or simply waiting for AcquireNextFrame to return would also work, although it would not benefit from specifying a small region.)

API proposal
Signature: D3DShot.wait_for_change(self, timeout=None)
Returns: True if a potantial change was detected, False if timeout was reached
Precondition: A high-speed screen capture was started. (i.e. capture() was called)
Description: Blocks until a potential change in the region is detected. Blocks at most timeout seconds. (None means forever.)
Note: The system may collapse several dirty rects into one giant rect, so spurious awakenings might happen. It means that if you need to be certain that the region has changed at all, you need to check it yourself.

The keyed mutex was abandoned

When any DX11 game in the fullscreen mode is focused - "The keyed mutex was abandoned." is thrown from the AcquireNextFrame. Library keeps producing last screenshot forever and unfortunately it does not recover from this state until the capturing process is restarted from scratch.

Also if capturing is started after you focus fullscreen game, it works fine. But the moment you alt-tab back to desktop, it fails.

Sceenshot active monitor

I know we can screenshot specific monitor by
d.display = d.displays[1]
I'm looking for a way to automatically screenshot the active monitor

Importing OpenCV alongside D3DShot results in COMError at create()

Hello,

I have run into a peculiar issue using this library (thanks for creating this btw!). When I try to import OpenCV (cv2 package) I run into this error at d3dshot.create():

(-2005270524, 'The specified device interface or feature level is not supported on this system.', (None, None, None, 0, None))

Here is my current code:

import d3dshot
import time
import cv2
d = d3dshot.create() #Error happens here

Removing the import cv2 line lets the code run without any errors. I have tried running the code as Administrator with same results.

This issue isn't urgent at all (I just wanted OpenCV for rendering, so I found a workaround) and seems to be a niche case, but I still wanted to leave this issue here in case others had similar issues.

Changing the resolution results in "frozen" screenshots - D3D doesn't update to current screen

When I change the resolution of my laptop after running my Python code with D3DShot, screenshot_to_disk_every() stops returning the current screen and instead returns the screen before resolution change. I don't know if this is an issue with Window's Desktop Duplication or this library.

My code:

import d3dshot
import time
import matplotlib.pyplot as plt
d = d3dshot.create()
d.screenshot_to_disk_every(10)

This issue occurs even with using other screenshot functions.

D3Dshot on linux platform

As I understand this library is only for Windows,

Will this be made for linux any time soon?

Receiving following error:

import d3dshot

File "/usr/local/lib/python3.7/dist-packages/d3dshot/init.py", line 4, in
from d3dshot.d3dshot import D3DShot
File "/usr/local/lib/python3.7/dist-packages/d3dshot/d3dshot.py", line 8, in
from d3dshot.display import Display
File "/usr/local/lib/python3.7/dist-packages/d3dshot/display.py", line 1, in
import d3dshot.dll.dxgi
File "/usr/local/lib/python3.7/dist-packages/d3dshot/dll/dxgi.py", line 2, in
import ctypes.wintypes as wintypes
File "/usr/lib/python3.7/ctypes/wintypes.py", line 20, in
class VARIANT_BOOL(ctypes._SimpleCData):
ValueError: type 'v' not supported

Do we own the memory pointed to by the returned screenshots? Also: Warning about using the screenshots with OpenCV!

Hey, great library, huge thanks for all of your great work!

So, I'm curious... When we grab images, it's internally done by asking Windows via COM to make a screenshot, and then return the pointer to us...

What does D3DShot do with that pointer? Does it copy the data to our own internal memory within Python, or do we continue to use the Windows pointer to memory owned by Windows?

The reason why I ask is that I've seen some really strange behavior. Perhaps a bug in d3dshot?

import cv2
import d3dshot
import numpy as np

d = d3dshot.create(capture_output="numpy")
img = d.screenshot()
print(img.flags.writeable)
img[:,:,1] = 255 # Write maximum green into every pixel (makes img hue green)
#img = img.copy() # You must uncomment this, otherwise cv2 can't draw on it.
cv2.rectangle(img, (0,0), (100,100), (0,0,255), 6)

cv2.imshow("img", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

As the comment/code explains:

  • The Numpy array returned by d.screenshot() is marked as writeable.
  • Writing directly into the array with img[:,:,1] = 255 works perfectly (fills the green channel with 255 in every pixel).
  • Passing the img Numpy array to cv2.rectangle does NOT draw on the image.
  • The only way to draw on the image is to FIRST copy it to a brand new Numpy object and THEN pass the NEW Numpy object to OpenCV.

This is very strange. It seems kinda like a bug. I really can't understand why the Numpy object is marked as writeable and we're able to write to it manually, but OpenCV cannot write to it unless we first copy it. So clearly SOMETHING differs between these objects, but I have no idea what...


Actually, I might have just solved it while writing this ticket, thanks to knowing quite a lot about OpenCV internals... Okay, if you look at print(img.flags), the raw screenshot says "CONTIGUOUS = FALSE" which means that the underlying RAM is not laid out in the same way as what the Numpy array is claiming. (You can read more about RAM layout and Contiguous in my article here: https://answers.opencv.org/question/219040/fastest-way-to-convert-bgr-rgb-aka-do-not-use-numpy-magic-tricks/)

So, the following code shows us what is going on:

img = d.screenshot()
print(img.flags) # Contiguous: False, Writeable: True
print(img.strides) # (1920, 1, 2073600)
img = img.copy() # Makes a new, contiguous object
print(img.strides) # (5760, 3, 1)

Okay, the next part of understanding this problem is to know about what OpenCV does internally when it receives a Python object. That's described in the link I gave above. Basically, if you give OpenCV a non-contiguous Numpy object, it can't read the RAM as-is. Instead, it creates a new Numpy object with contiguous RAM storage and then reads THAT object's RAM data, and passes it into the internal OpenCV function (ie draw rectangle).

So, OpenCV is taking the screenshot Numpy object (which is incorrectly laid out in RAM), and then it is creating a new (temporary) Numpy object with proper layout, and then it passes that object to the internal C++ based drawing function.

In other words, we're drawing on a temporary object.

Here's the proof:

import cv2
import d3dshot
import numpy as np
import time

d = d3dshot.create(capture_output="numpy")
img = d.screenshot()
#img = img.copy() # Uncomment this to check speed difference!
start = time.perf_counter()
cv2.rectangle(img, (0,0), (100,100), (0,0,255), 6)
#cv2.dnn.blobFromImage(img)
elapsed = (time.perf_counter() - start) * 1000
print("elapsed:", elapsed, "ms")

cv2.imshow("img", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

Results: If we're passing the original image given by d3dshot into cv2.rectangle, the costly conversion happens in OpenCV's "Python-to-OpenCV" wrapper... and I am getting 148.2 ms runtime for a call to cv2.rectangle. If we instead first do img = img.copy() to make a new, contiguous Numpy array, the cv2.rectangle completes in just 0.09 ms.

I also checked with blobFromImage and the time was 37 ms if img.copy() was first used, and 46 ms otherwise (meaning with the bad RAM data layout). So, around +9 ms to call the blob converter when given badly laid out RAM. Note that the "9ms" is no coincidence. That's the average amount of time it takes to run img = img.copy(), which confirms that blobFromImage has to create a fixed (contiguous) temporary, internal copy of the Numpy array when you call that function.

So yes, it is now confirmed:

D3DShot Numpy Arrays are HARMFUL if used with OpenCV. Every time you call ANY OpenCV function and give it the original d3dshot Numpy array, you are causing a MASSIVE slowdown since
the data has to be internally converted by OpenCV to a temporary Numpy array with corrected memory layout.

What's the solution then? Well, there are a few solutions. All involve various ways to pre-build a contiguous ndarray so that OpenCV doesn't have to do any conversion when you give it the ndarray later.

  • img = img.copy(), which takes 9.3 milliseconds on my i7-8750H Laptop at 1920x1080.
  • img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) to actually use the OpenCV color converter. This takes 11.3 milliseconds consistently on my computer (of that, 9.3 milliseconds is the time it takes Numpy to copy the non-contiguous data to a contiguous one first, which happens internally in OpenCV when given non-contiguous ndarrays). And this method is obviously only useful if you WANT to change the channel order.
  • Perhaps it can be fixed in D3DShot itself, by making it lay out the data contiguously in RAM? I doubt it, but it's worth asking. If we're doing a memory copy of the temporary data that Windows gave us over COM, then perhaps we can do that copy in a way that lays it out contiguously in RAM. That way, the Numpy arrays would be valid for OpenCV API usage, without needing conversion overhead.

Multiple instances of d3dshot.create = Impossible

import d3dshot
a = d3dshot.create()
b = d3dshot.create()
Traceback (most recent call last):
  File "C:\Users\Secret\Downloads\demo.py", line 3, in <module>
    b = d3dshot.create()
  File "C:\Users\Secret\AppData\Local\Programs\Python\Python37\lib\site-packages\d3dshot\__init__.py", line 70, in create
    pytorch_gpu_is_available=pytorch_gpu_is_available
  File "C:\Users\Secret\AppData\Local\Programs\Python\Python37\lib\site-packages\d3dshot\d3dshot.py", line 24, in __init__
    self.detect_displays()
  File "C:\Users\Secret\AppData\Local\Programs\Python\Python37\lib\site-packages\d3dshot\d3dshot.py", line 194, in detect_displays
    self.displays = Display.discover_displays()
  File "C:\Users\Secret\AppData\Local\Programs\Python\Python37\lib\site-packages\d3dshot\display.py", line 119, in discover_displays
    dxgi_adapter=dxgi_adapter
  File "C:\Users\Secret\AppData\Local\Programs\Python\Python37\lib\site-packages\d3dshot\display.py", line 40, in __init__
    self.dxgi_output_duplication = self._initialize_dxgi_output_duplication()
  File "C:\Users\Secret\AppData\Local\Programs\Python\Python37\lib\site-packages\d3dshot\display.py", line 66, in _initialize_dxgi_output_duplication
    return d3dshot.dll.dxgi.initialize_dxgi_output_duplication(self.dxgi_output, self.d3d_device)
  File "C:\Users\Secret\AppData\Local\Programs\Python\Python37\lib\site-packages\d3dshot\dll\dxgi.py", line 257, in initialize_dxgi_output_duplication
    dxgi_output.DuplicateOutput(d3d_device, ctypes.byref(dxgi_output_duplication))
_ctypes.COMError: (-2147024809, 'The parameter is incorrect.', (None, None, None, 0, None))

Would this be solvable by making d3dshot share some memory between all instances of the class? For example, the d3dshot module's namespace can be used for storing a single variable that is used by all files that import d3dshot. That way, you could store a single COM connection used by all instances of the class, etc... It may be possible to make multiple instances play nicely together that way...

Personally, I made a wrapper module around d3dshot, which just basically does something like this (mine is class-based, this is just an example):

import d3dshot

ScreenGrabber = d3dshot.create()

Then I can from blah import ScreenGrabber from any other file and can use ScreenGrabber.screenshot() etc, all without getting COM errors, since they all share the exact same connection.

The important thing is to store d3dshot.create as a static variable in the module itself (outside any classes). And then ensuring that everything else only interacts with that single connection.

Is this maintained? feature query

Hi, just wondering if this repo is still being maintained and if there is a way to capture last frame with a region.
screenshot works for my project but seems to be delayed slightly.

Getting garbled image on screenshot

Hi,

I´ve been trying to use D3DShot but it´s been returning a garbled image, like this:

I´m thinking this has something to do with drivers, any thoughts?

RuntimeError: deque mutated during iteration

import d3dshot
import time

d = d3dshot.create(capture_output="numpy")
d.capture(region=(100,100,300,300), target_fps=30)
time.sleep(20)
d.stop()
d.frame_buffer_to_disk()

dumping frame buffer to disk always yields following error:
File "C:\ProgramData\Anaconda3\envs\testing_env\lib\site-packages\d3dshot\d3dshot.py", line 119, in frame_buffer_to_disk for i, frame in enumerate(self.frame_buffer): RuntimeError: deque mutated during iteration

Investigate WindowsGraphicsCapture API

As discovered by using OBS Studio, there is a new Window API for screen capture that has great extra features like capturing windows even if they are occluded or moved.

Investigate WindowsGraphicsCapture to determine if:

  • It can be hooked into by Python
  • It has good performance
  • It is compatible with the existing D3DShot API

get_latest_frame() returns None

I think d.get_latest_frame() doesn't waits for the frame buffer to populate after d.capture() is called.

image

While manually waiting for that resolves the issue:

image

Just curious if the checks have intentionally been ignored considering performance....

AttributeError: 'NoneType' object has no attribute 'capture'

Hello!

I'm trying to use the basic examples, but when trying to create a screenshot with:

import d3dshot
d = d3dshot.create()
d.screenshot()

I'm getting

File "", line 1, in
File "C:\Users\Administrator\AppData\Local\Programs\Python\Python37\lib\site-packages\d3dshot\d3dshot.py", line 89, in screenshot
frame = self.display.capture(self.capture_output.process, region=region)
AttributeError: 'NoneType' object has no attribute 'capture'

What am I doing wrong?

COMError at create()

import time
import d3dshot

screen = d3dshot.create(capture_output="numpy")
  File "C:\Users\mehmet\AppData\Roaming\Python\Python37\site-packages\d3dshot\display.py", line 69, in _initialize_dxgi_output_duplication
    self.dxgi_output, self.d3d_device
  File "C:\Users\mehmet\AppData\Roaming\Python\Python37\site-packages\d3dshot\dll\dxgi.py", line 284, in initialize_dxgi_output_duplication
    dxgi_output.DuplicateOutput(d3d_device, ctypes.byref(dxgi_output_duplication))
_ctypes.COMError: (-2005270524, 'Belirtilen aygıt arabirimi veya özellik düzeyi bu sistemde desteklenmiyor.', (None, None, None, 0, None))

Thats Turkish 'Belirtilen aygıt arabirimi veya özellik düzeyi bu sistemde desteklenmiyor." but in English "The specified device interface or feature level is not supported on this system."

RTX2060, Laptop, Windows 10 version 2004

I unistalled nvidia driver 452.06 and its work but with drivers it dosent work!

import time
import d3dshot
import cv2

screen = d3dshot.create(capture_output="numpy")
print(screen.display)

image

[WinError 126] The specified module could not be found (shcore)

Windows 7
Python 3.7 64bit
pip install d3dshot

Collecting d3dshot
  Using cached https://files.pythonhosted.org/packages/e8/8d/64838f60f2a3b080024b9774673148020ec2893398c0d1ca41f12d6439f6/D3DShot-0.1.3-py3-none-any.whl
Requirement already satisfied: Pillow>=5.0.0 in ...\lib\site-packages (from d3dshot) (5.2.0)
Requirement already satisfied: comtypes>=1.1.5 in ...\lib\site-packages (from d3dshot) (1.1.7)
Installing collected packages: d3dshot
Successfully installed d3dshot-0.1.3

When running samples from Readme.md:

  File ".test.py", line 3, in <module>
    d = d3dshot.create()
  File "...\lib\site-packages\d3dshot\__init__.py", line 70, in create
    pytorch_gpu_is_available=pytorch_gpu_is_available
  File "...\lib\site-packages\d3dshot\d3dshot.py", line 24, in __init__
    self.detect_displays()
  File "...\lib\site-packages\d3dshot\d3dshot.py", line 194, in detect_displays
    self.displays = Display.discover_displays()
  File "...\lib\site-packages\d3dshot\display.py", line 107, in discover_displays
    scale_factor = d3dshot.dll.shcore.get_scale_factor_for_monitor(hmonitor)
  File "...\lib\site-packages\d3dshot\dll\shcore.py", line 7, in get_scale_factor_for_monitor
    ctypes.windll.shcore.GetScaleFactorForMonitor(hmonitor, ctypes.byref(scale_factor))
  File "...\lib\ctypes\__init__.py", line 426, in __getattr__
    dll = self._dlltype(name)
  File "...\lib\ctypes\__init__.py", line 356, in __init__
    self._handle = _dlopen(self._name, mode)
OSError: [WinError 126] The specified module could not be found

The self._name is "shcore" when the exception is raised

Screenshot from wrong display is sometimes returned

When capturing screenshots from multiple displays sometimes screenshot for wrong display is returned. The solution is to self.previous_screenshot in d3dshot.py be a dict with key of display or to move previous_screenshot to Display class.

Error: Access violation

Hi,

First off, thanks for making this brilliant library =)
I was unable to get it to work properly on my machine however, I'm getting a strange memory error when I run this simple code:

import d3dshot

d = d3dshot.create()
d.screenshot()
Traceback (most recent call last):
  File "C:/Users/coolq/OneDrive/Projects/Python/main.py", line 4, in <module>
    d = d3dshot.create()
  File "C:\Users\coolq\OneDrive\Projects\Python\venv\lib\site-packages\d3dshot\__init__.py", line 70, in create
    pytorch_gpu_is_available=pytorch_gpu_is_available
  File "C:\Users\coolq\OneDrive\Projects\Python\venv\lib\site-packages\d3dshot\d3dshot.py", line 23, in __init__
    self.detect_displays()
  File "C:\Users\coolq\OneDrive\Projects\Python\venv\lib\site-packages\d3dshot\d3dshot.py", line 192, in detect_displays
    self.displays = Display.discover_displays()
  File "C:\Users\coolq\OneDrive\Projects\Python\venv\lib\site-packages\d3dshot\display.py", line 85, in discover_displays
    dxgi_factory = d3dshot.dll.dxgi.initialize_dxgi_factory()
  File "C:\Users\coolq\OneDrive\Projects\Python\venv\lib\site-packages\d3dshot\dll\dxgi.py", line 189, in initialize_dxgi_factory
    create_factory_func(IDXGIFactory1._iid_, ctypes.byref(handle))
OSError: exception: access violation writing 0x4DBAF26F

I'm running Windows 10 Pro 64-bit, I have two screens driven by a GTX 1060.

I'm running this with Python 3.6.

Any ideas? 😮

Question

Very nice!

Tho I'd like to ask something directly, since you're more familiar with the code.
If I wanted to not receive duplicate frames when there's no change, how should I go about altering the functions so it'd return just the new data and a value of number of how many frames have been the same so far ? Thanks in advance :)

Errors in Virtual Machines

I am using the d3dshot module in a virtual machine.

The d3dshot module works normally the first time after booting the virtual machine, but it does not work after that.
Restarting after logging out of the session does not work.

I don't know what the problem is.

However, the following message occurs:
I've been trying for days to find the cause,
but I can't figure it out.

I need help~~~

========= Error Message ==================
Traceback (most recent call last):

File "C:\Program Files (x86)\BATEM\Common\Resource\embedded\python36.zip\runpy.py", line 193, in _run_module_as_main

File "C:\Program Files (x86)\BATEM\Common\Resource\embedded\python36.zip\runpy.py", line 85, in _run_code

File "C:\Program Files (x86)\BATEM\Common\Resource\engine\rpa\library\screen_capture.py", line 5, in

__INSTANCE__ = d3dshot.create()

File "C:\Program Files (x86)\BATEM\Common\Resource\embedded\lib\site-packages\d3dshot_init_.py", line 74, in create

pytorch_gpu_is_available=pytorch_gpu_is_available,

File "C:\Program Files (x86)\BATEM\Common\Resource\embedded\lib\site-packages\d3dshot\d3dshot.py", line 17, in call

cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)

File "C:\Program Files (x86)\BATEM\Common\Resource\embedded\lib\site-packages\d3dshot\d3dshot.py", line 37, in init

self.detect_displays()

File "C:\Program Files (x86)\BATEM\Common\Resource\embedded\lib\site-packages\d3dshot\d3dshot.py", line 216, in detect_displays

self.displays = Display.discover_displays()

File "C:\Program Files (x86)\BATEM\Common\Resource\embedded\lib\site-packages\d3dshot\display.py", line 129, in discover_displays

dxgi_adapter=dxgi_adapter,

File "C:\Program Files (x86)\BATEM\Common\Resource\embedded\lib\site-packages\d3dshot\display.py", line 39, in init

self.dxgi_output_duplication = self._initialize_dxgi_output_duplication()

File "C:\Program Files (x86)\BATEM\Common\Resource\embedded\lib\site-packages\d3dshot\display.py", line 69, in _initialize_dxgi_output_duplication

self.dxgi_output, self.d3d_device

File "C:\Program Files (x86)\BATEM\Common\Resource\embedded\lib\site-packages\d3dshot\dll\dxgi.py", line 284, in initialize_dxgi_output_duplication

dxgi_output.DuplicateOutput(d3d_device, ctypes.byref(dxgi_output_duplication))

ctypes.COMError: (-2147418113, 'Error', (None, None, None, 0, None))

Benchmark results

Im getting the dame results un benchmarks when i run It with numpy and when i run It with default PIL. Both configurations achieve 78 FPS. Is this working as expected?
Thanks in advanced.

Do you have plan to add audio capture?

Thank you very much for the great work on this python lib. Could I suggest that it'll be even greater if you can add audio capture too? This can make it richer in feature and more useful.

Thanks,
Quan

Create an anaconda package

Since this is a Windows specific solution, it makes sense to create a anaconda package rather that just pip.

Any chance of 32 bit support?

I realise that the bit-ness of python needs to match the bit-ness of the underlying Windows install. What if we're using 32-bit python in 32-bit windows?

1,000,000+ fps in screen capture

How can I start capturing on the same thread as my code?
Because getting the last frame is not an option, since I cannot find out when it changed.

example:

import d3dshot
from time import sleep, time
d = d3dshot.create(capture_output="numpy", frame_buffer_size=90)
d.capture(region=(653, 354, 713, 404))
sleep(1)
fps = 0
display_time = 1
start_time = 0
while True:
    test = d.get_latest_frame()
    fps+=1
    TIME = time() - start_time
    if (TIME) >= display_time :
        print("FPS: ", int(fps / (TIME)))
        start_time = time()
        fps = 0

Output:

FPS:  1562927
FPS:  1544829
FPS:  1702034

wtf? 1000000 fps?

Capture window

Hi there.

First of all thanks for library.
This is more of a feature request - I didn't find it - but it would be nice to be able capture just the window of any opened/selected program.

Inverted Colors

I can't tell if this is open cv or this library, but one of the channels is messed up

image

vs what it's supposed to look like:

image

I didn't see this issue with other libraries, but I might still be doing something wrong

Capturing window rather than desktop?

I realise I could write my own wrapper that uses win32 to identify a windows rect/monitor then call d3dshots api with the right settings, however two cases aren't handled:

  1. if the window moves (I think I'd write a wrapper to check window moving and then reinit d3dshots with right arguments)
  2. if there is another window covering the target window.

Windows bitblt handles this fine,I was wondering if d3d can handle this (and it's just a matter of implementation) or if d3d can only do full screen capture followed by cropping?

How does OBS studio handle window capture? Bitblt or d3d?

Investigate capturing in a separate process and exposing the frame buffer with Python 3.8's shared memory capabilities

Probably better to escape the GIL at the library level rather than placing the burden on the user.

This wasn't initially considered because the performance loss of IPC with serialization/deserialization steps would have been too important.

With Python 3.8's shared memory capabilities, it might be possible to copy the frame to the main thread with very little overhead and therefore be able to process it without the overhead having to run on the same CPU core.

Support for resizing (downscaling) the output

In a lot of use cases it's often better to have a resized output that's smaller than the native resolution, of course we can do this manually after the output is received, but it would be much faster to be able to down sample on GPU and then output. I'm not at all familiar with DirectX, but from what I gathered so far, we can either do this by creating and outputing mipmaps of the original frame, re-render the original frame to a smaller target-sized texture, or use some sort of compute shader to do the scaling manually. I'm not sure which way would be the fastest, from what I've learned so far, I'm guessing mipmaps would be the fastest if the scale factor is a power of two.

I'm not sure about how to approach the other methods, but mipmaps should be relatively easy to implement. However, I couldn't find a way to generate the appropriate methods & classes used by DirectX's mipmaps stuff, I tried using comtypes' GetModule('d3d11.dll'), but it doesn't seem to work and crashes with "Error loading type library/DLL".

The specified device interface or feature level is not supported on this system

Hi
I encounter a problem in my W10 64bit laptop P51s, it's two video cards, nvidia and integrated intel.
I've setup nvdia as primary use and Dxdiag shows that Directx 12 is running normally.
Here is my code:

import time
import numpy as np
import d3dshot
p1=time.time()
d = d3dshot.create(capture_output="numpy")
img2=d.screenshot()
p2=time.time()
print ("it takes "+str(p2-p1)+ " seconds to capture in D3D.")

Traceback (most recent call last):
File "C:/Users/jianghu/PycharmProjects/diaexplo/mess_d3d.py", line 14, in
d = d3dshot.create()
File "C:\Python38\lib\site-packages\d3dshot_init_.py", line 64, in create
d3dshot = D3DShot(
File "C:\Python38\lib\site-packages\d3dshot\d3dshot.py", line 24, in init
self.detect_displays()
File "C:\Python38\lib\site-packages\d3dshot\d3dshot.py", line 194, in detect_displays
self.displays = Display.discover_displays()
File "C:\Python38\lib\site-packages\d3dshot\display.py", line 109, in discover_displays
display = cls(
File "C:\Python38\lib\site-packages\d3dshot\display.py", line 40, in init
self.dxgi_output_duplication = self._initialize_dxgi_output_duplication()
File "C:\Python38\lib\site-packages\d3dshot\display.py", line 66, in _initialize_dxgi_output_duplication
return d3dshot.dll.dxgi.initialize_dxgi_output_duplication(self.dxgi_output, self.d3d_device)
File "C:\Python38\lib\site-packages\d3dshot\dll\dxgi.py", line 257, in initialize_dxgi_output_duplication
dxgi_output.DuplicateOutput(d3d_device, ctypes.byref(dxgi_output_duplication))
_ctypes.COMError: (-2005270524, 'The specified device interface or feature level is not supported on this system.', (None, None, None, 0, None))

Process finished with exit code 1

Could you suggest what I should do to fix the issue?

capture() fails with WinError[995]

I am trying to get high speed capture using capture->delay->stop. When calling creat(), I always got this exception:

Unhandled exception in event loop:
  File "C:\Python38\lib\asyncio\proactor_events.py", line 768, in _loop_self_reading
    f.result()  # may raise
  File "C:\Python38\lib\asyncio\windows_events.py", line 808, in _poll
    value = callback(transferred, key, ov)
  File "C:\Python38\lib\asyncio\windows_events.py", line 457, in finish_recv
    raise ConnectionResetError(*exc.args)

Exception [WinError 995] The I/O operation has been aborted because of either a thread exit or an application request
Press ENTER to continue...

This issue seems only to impact the ipython environment.

Code doesn't work

windows 10
screen 1366x768
Notebook (with nvidia 940m)
python 3.8

I tried to run the code (Any) and it doesn't work for me.

Traceback (most recent call last):
  File "E:\NEWEXEC2\Hello.py", line 3, in <module>
    d = d3dshot.create(capture_output="numpy")
  File "C:\Users\andre\AppData\Local\Programs\Python\Python38\lib\site-packages\d3dshot\__init__.py", line 68, in create
    d3dshot = D3DShot(
  File "C:\Users\andre\AppData\Local\Programs\Python\Python38\lib\site-packages\d3dshot\d3dshot.py", line 17, in __call__
    cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
  File "C:\Users\andre\AppData\Local\Programs\Python\Python38\lib\site-packages\d3dshot\d3dshot.py", line 37, in __init__
    self.detect_displays()
  File "C:\Users\andre\AppData\Local\Programs\Python\Python38\lib\site-packages\d3dshot\d3dshot.py", line 215, in detect_displays
    self.displays = Display.discover_displays()
  File "C:\Users\andre\AppData\Local\Programs\Python\Python38\lib\site-packages\d3dshot\display.py", line 119, in discover_displays
    display = cls(
  File "C:\Users\andre\AppData\Local\Programs\Python\Python38\lib\site-packages\d3dshot\display.py", line 39, in __init__
    self.dxgi_output_duplication = self._initialize_dxgi_output_duplication()
  File "C:\Users\andre\AppData\Local\Programs\Python\Python38\lib\site-packages\d3dshot\display.py", line 68, in _initialize_dxgi_output_duplication
    return d3dshot.dll.dxgi.initialize_dxgi_output_duplication(
  File "C:\Users\andre\AppData\Local\Programs\Python\Python38\lib\site-packages\d3dshot\dll\dxgi.py", line 284, in initialize_dxgi_output_duplication
    dxgi_output.DuplicateOutput(d3d_device, ctypes.byref(dxgi_output_duplication))
_ctypes.COMError: (-2005270524, 'Указанный интерфейс устройства или уровень компонента не поддерживается в данной системе.', (None, None, None, 0, None))

Slow performance

I just tried out D3DShot on my PC and the performance is pretty slow. My native display resolution is 3840x2160, and the graphics card is Nvidia GTX 1080, the average fps I'm getting is about 20. I tried benchmark with both the included benchmark method, and by counting the total frames added. Both yield similarly slow performance. Below is one of the many benchmark runs:

Capture Output: NumpyCaptureOutput
Display: <Display name=Generic PnP Monitor adapter=NVIDIA GeForce GTX 1080 resolution=3840x2160 rotation=0 scale_factor=2.5 primary=True>

Capturing as many frames as possible in the next 60 seconds... Go!
Done! Results: 20.4833 FPS

I looked at other issues with D3DShots and tried out some ways to improve this, for example, I tried running a Windowed or Fullscreen game when benchmarking, I also tried just staying in normal desktop environment, both yield similar results.

My graphics card driver is up-to-date (GeForce 460.89, 12/15/2020), my Windows 10 version is 2004, build 21277.1000.

To clarify, everything else graphics related is normal, games would run at normal FPS (60+), and nothing lags or is slow.

Can you help me understand region?

@nbrochu I'm trying to wrap my head around region and clearly failing. mss has something like:

{"top": 332, "left": 752, "width": 416, "height": 416}

I have a 416 by 416 block offset top by 332 and left by 752. This gives me a 416 by 416 block dead center of the screen.

I cannot get the same thing using this project. Could you give me a pointer, im sure I'm just being dumb

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.