Use SharpDX to capture screenshot of rotated monitor
First, I only used SharpDX for a few days, so I'm by no means an expert, but I ran into a similar problem when capturing from rotated monitor and from what I've been able to deduce captured frame is not rotated.
e.g. Your monitor is rotated 90 deg to portrait (Width x Height 1080x1920) so you'd expect the captured frame to be portrait as well, right? Nope, you get the 1920 x 1080 landscape bitmap so your screen width= bitmap height and vice versa:
Here is the code I used in my capture class, still work in progress:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using SharpDX;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using SharpDX.Mathematics.Interop;
using Device = SharpDX.Direct3D11.Device;
using MapFlags = SharpDX.Direct3D11.MapFlags;
namespace EXM.ExampleCapture
{
public class DXScreenCaptureUtil {
private static ImageCodecInfo jpegCodec = ImageCodecInfo.GetImageEncoders()
.First(c => c.FormatID == ImageFormat.Jpeg.Guid);
private static EncoderParameters jpegParams = new EncoderParameters() { Param = new[] { new EncoderParameter(Encoder.Quality, 60L) } };
//Cache objects
private static Factory1 factory = new Factory1();
private static Adapter adapter;
private static Device device;
/// <summary>
/// Gets target device (Display) based on the rectangle we want to capture
/// </summary>
/// <param name="sourceRect">Rectangle we want to capture</param>
/// <returns>Screen which contains the area we want to capture or null if no device contains our area of interest</returns>
private static Screen GetTargetScreen(Rectangle sourceRect) {
foreach (var scr in Screen.AllScreens)
{
if (sourceRect.X >= scr.Bounds.X && sourceRect.Y >= scr.Bounds.Y
&& sourceRect.Right <= scr.Bounds.Width + scr.Bounds.X
&& sourceRect.Bottom <= scr.Bounds.Height + scr.Bounds.Y
)
{
return scr;
}
}
return null;
}
public static (byte[], int) Capture(Rectangle sourceRect, int jpegQuality) {
Screen targetScreen = GetTargetScreen(sourceRect);
if (targetScreen == null) {
throw new Exception($@"Could not find target screen for capture rectangle {sourceRect}");
}
//This is to instruct client receiving the image to rotate it, seems like a reasonable thing to offload it to client and save a bit of CPU time on server
int rotation = 0;
byte[] imageBytes = null;
// Width/Height of desktop to capture
int width = targetScreen.Bounds.Width;
int height = targetScreen.Bounds.Height;
Rectangle cropRect = new Rectangle(sourceRect.X - targetScreen.Bounds.X, sourceRect.Y - targetScreen.Bounds.Y, sourceRect.Width, sourceRect.Height);
// Create DXGI Factory1
if (adapter == null) { adapter = factory.Adapters.Where(x => x.Outputs.Any(o => o.Description.DeviceName == targetScreen.DeviceName)).FirstOrDefault(); }
// Create device from Adapter
if (device == null) { device = new Device(adapter); }
//using (var output = adapter.Outputs.Where(o => o.Description.DeviceName == targetScreen.DeviceName).FirstOrDefault()) //This creates a memory leak!
Output output = null;
//I'm open to suggestions here:
for (int i = 0; i < adapter.GetOutputCount(); i++) {
output = adapter.GetOutput(i);
if (output.Description.DeviceName == targetScreen.DeviceName) {
break;
}
else {
output.Dispose();
}
}
using (var output1 = output.QueryInterface<Output1>()) {
if (output1.Description.Rotation == DisplayModeRotation.Rotate90) {
width = targetScreen.Bounds.Height;
height = targetScreen.Bounds.Width;
int offsetX = targetScreen.Bounds.X - sourceRect.X;
cropRect = new Rectangle(
sourceRect.Y - targetScreen.Bounds.Y,
targetScreen.Bounds.Width - (sourceRect.Width + offsetX),
sourceRect.Height, sourceRect.Width);
rotation = 90;
}
else if (output1.Description.Rotation == DisplayModeRotation.Rotate270) {
width = targetScreen.Bounds.Height;
height = targetScreen.Bounds.Width;
int offsetY = targetScreen.Bounds.Y - sourceRect.Y;
cropRect = new Rectangle(
targetScreen.Bounds.Height - (sourceRect.Height + offsetY),
targetScreen.Bounds.X - sourceRect.X,
sourceRect.Height, sourceRect.Width);
rotation = 270;
}
else if (output1.Description.Rotation == DisplayModeRotation.Rotate180) {
rotation = 180;
}
// Create Staging texture CPU-accessible
var textureDesc = new Texture2DDescription {
CpuAccessFlags = CpuAccessFlags.Read,
BindFlags = BindFlags.None,
Format = Format.B8G8R8A8_UNorm,
Width = width,
Height = height,
OptionFlags = ResourceOptionFlags.None,
MipLevels = 1,
ArraySize = 1,
SampleDescription = { Count = 1, Quality = 0 },
Usage = ResourceUsage.Staging
};
using (var screenTexture = new Texture2D(device, textureDesc))
//Duplicate the output
using (var duplicatedOutput = output1.DuplicateOutput(device)) {
bool captureDone = false;
SharpDX.DXGI.Resource screenResource = null;
OutputDuplicateFrameInformation duplicateFrameInformation;
for (int i = 0; !captureDone; i++) {
try {
//Try to get duplicated frame within given time
duplicatedOutput.AcquireNextFrame(1000, out duplicateFrameInformation, out screenResource);
//Ignore first call, this always seems to return a black frame
if (i == 0) {
screenResource.Dispose();
continue;
}
//copy resource into memory that can be accessed by the CPU
using (var screenTexture2D = screenResource.QueryInterface<Texture2D>()) {
device.ImmediateContext.CopyResource(screenTexture2D, screenTexture);
}
//Get the desktop capture texture
var mapSource = device.ImmediateContext.MapSubresource(screenTexture, 0, MapMode.Read, MapFlags.None);
var boundsRect = new System.Drawing.Rectangle(0, 0, width, height);
//Create Drawing.Bitmap
using (var bitmap = new System.Drawing.Bitmap(width, height, PixelFormat.Format32bppArgb)) {
//Copy pixels from screen capture Texture to GDI bitmap
var bitmapData = bitmap.LockBits(boundsRect, ImageLockMode.WriteOnly, bitmap.PixelFormat);
var sourcePtr = mapSource.DataPointer;
var destinationPtr = bitmapData.Scan0;
for (int y = 0; y < height; y++) {
//Copy a single line
Utilities.CopyMemory(destinationPtr, sourcePtr, width * 4);
//Advance pointers
sourcePtr = IntPtr.Add(sourcePtr, mapSource.RowPitch);
destinationPtr = IntPtr.Add(destinationPtr, bitmapData.Stride);
}
//Release source and dest locks
bitmap.UnlockBits(bitmapData);
device.ImmediateContext.UnmapSubresource(screenTexture, 0);
//Save the output
imageBytes = CropBitmapToJPEGBytes(bitmap, cropRect, jpegQuality);
}
//Capture done
captureDone = true;
}
catch (SharpDXException e) {
if (e.ResultCode.Code != SharpDX.DXGI.ResultCode.WaitTimeout.Result.Code) {
throw;
}
}
finally {
//Dispose manually
if (screenResource != null) {
screenResource.Dispose();
}
duplicatedOutput.ReleaseFrame();
}
}
}
}
output.Dispose();
return (imageBytes, rotation);
}
/// <summary>
/// Crop bitmap
/// </summary>
/// <param name="orig">Original bitmap</param>
/// <param name="cropRect">Crop rectangle</param>
/// <returns>Cropped bitmap</returns>
static byte[] CropBitmapToJPEGBytes(Bitmap orig, Rectangle cropRect, int jpegQuality) {
EncoderParameter qualityParam = new EncoderParameter(Encoder.Quality, (long)jpegQuality);
jpegParams.Param[0] = qualityParam;
byte[] imageBytes;
using (Bitmap nb = new Bitmap(cropRect.Width, cropRect.Height)) {
using (Graphics g = Graphics.FromImage(nb)) {
g.DrawImage(orig, -cropRect.X, -cropRect.Y);
using (MemoryStream s = new MemoryStream()) {
nb.Save(s, jpegCodec, jpegParams);
imageBytes = s.ToArray();
}
}
}
return imageBytes;
}
}
}