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: enter image description here

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;
        }
    }
    
}