Resizing a DXGI Resource or Texture2D in SharpDX
Asked Answered
H

3

8

I want to resize a screen captured using the Desktop Duplication API in SharpDX. I am using the Screen Capture sample code from the SharpDX Samples repository, relevant portion follows:.

SharpDX.DXGI.Resource screenResource;
OutputDuplicateFrameInformation duplicateFrameInformation;

// Try to get duplicated frame within given time
duplicatedOutput.AcquireNextFrame(10000, out duplicateFrameInformation, out screenResource);

if (i > 0)
{
    // 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);

    System.Diagnostics.Debug.WriteLine(watch.Elapsed);

    // Create Drawing.Bitmap
    var bitmap = new System.Drawing.Bitmap(width, height, PixelFormat.Format32bppArgb);
    var boundsRect = new System.Drawing.Rectangle(0, 0, width, height);

    // Copy pixels from screen capture Texture to GDI bitmap
    var mapDest = bitmap.LockBits(boundsRect, ImageLockMode.WriteOnly, bitmap.PixelFormat);
    var sourcePtr = mapSource.DataPointer;
    var destPtr = mapDest.Scan0;
    for (int y = 0; y < height; y++)
    {
        // Iterate and write to bitmap...

I would like to resize the image much smaller than the actual screen size before processing it as a byte array. I do not need to save the image, just get at the bytes. I would like to do this relatively quickly and efficiently (e.g. leveraging GPU if possible).

I'm not able to scale during CopyResource, as the output dimensions are required to be the same as the input dimensions. Can I perform another copy from my screenTexture2D to scale? How exactly do I scale the resource - do I use a Swap Chain, Matrix transform, or something else?

Harkins answered 5/6, 2014 at 15:57 Comment(0)
R
7

If you are fine resizing to a power of two from the screen, you can do it by:

  • Create a smaller texture with RenderTarget/ShaderResource usage, and options GenerateMipMaps, same size of screen, mipcount > 1 (2 for having size /2, 3 for having /4...etc.).
  • Copy the first mipmap of the screen texture to the smaller texture
  • DeviceContext.GenerateMipMaps on the smaller texture
  • Copy the selected mimap of the smaller texture (1: /2, 2: /4...etc.) to the staging texture (that should also be declared smaller, i.e. same size as the mipmap that is going to be used)

A quick hack on the original code to generate a /2 texture would be like this:

    [STAThread]
    private static void Main()
    {
        // # of graphics card adapter
        const int numAdapter = 0;

        // # of output device (i.e. monitor)
        const int numOutput = 0;

        const string outputFileName = "ScreenCapture.bmp";

        // Create DXGI Factory1
        var factory = new Factory1();
        var adapter = factory.GetAdapter1(numAdapter);

        // Create device from Adapter
        var device = new Device(adapter);

        // Get DXGI.Output
        var output = adapter.GetOutput(numOutput);
        var output1 = output.QueryInterface<Output1>();

        // Width/Height of desktop to capture
        int width = output.Description.DesktopBounds.Width;
        int height = output.Description.DesktopBounds.Height;

        // Create Staging texture CPU-accessible
        var textureDesc = new Texture2DDescription
                              {
                                  CpuAccessFlags = CpuAccessFlags.Read,
                                  BindFlags = BindFlags.None,
                                  Format = Format.B8G8R8A8_UNorm,
                                  Width = width/2,
                                  Height = height/2,
                                  OptionFlags = ResourceOptionFlags.None,
                                  MipLevels = 1,
                                  ArraySize = 1,
                                  SampleDescription = { Count = 1, Quality = 0 },
                                  Usage = ResourceUsage.Staging
                              };
        var stagingTexture = new Texture2D(device, textureDesc);

        // Create Staging texture CPU-accessible
        var smallerTextureDesc = new Texture2DDescription
        {
            CpuAccessFlags = CpuAccessFlags.None,
            BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,
            Format = Format.B8G8R8A8_UNorm,
            Width = width,
            Height = height,
            OptionFlags = ResourceOptionFlags.GenerateMipMaps,
            MipLevels = 4,
            ArraySize = 1,
            SampleDescription = { Count = 1, Quality = 0 },
            Usage = ResourceUsage.Default
        };
        var smallerTexture = new Texture2D(device, smallerTextureDesc);
        var smallerTextureView = new ShaderResourceView(device, smallerTexture);

        // Duplicate the output
        var duplicatedOutput = output1.DuplicateOutput(device);

        bool captureDone = false;
        for (int i = 0; !captureDone; i++)
        {
            try
            {
                SharpDX.DXGI.Resource screenResource;
                OutputDuplicateFrameInformation duplicateFrameInformation;

                // Try to get duplicated frame within given time
                duplicatedOutput.AcquireNextFrame(10000, out duplicateFrameInformation, out screenResource);

                if (i > 0)
                {
                    // copy resource into memory that can be accessed by the CPU
                    using (var screenTexture2D = screenResource.QueryInterface<Texture2D>())
                        device.ImmediateContext.CopySubresourceRegion(screenTexture2D, 0, null, smallerTexture, 0);

                    // Generates the mipmap of the screen
                    device.ImmediateContext.GenerateMips(smallerTextureView);

                    // Copy the mipmap 1 of smallerTexture (size/2) to the staging texture
                    device.ImmediateContext.CopySubresourceRegion(smallerTexture, 1, null, stagingTexture, 0);

                    // Get the desktop capture texture
                    var mapSource = device.ImmediateContext.MapSubresource(stagingTexture, 0, MapMode.Read, MapFlags.None);

                    // Create Drawing.Bitmap
                    var bitmap = new System.Drawing.Bitmap(width/2, height/2, PixelFormat.Format32bppArgb);
                    var boundsRect = new System.Drawing.Rectangle(0, 0, width/2, height/2);

                    // Copy pixels from screen capture Texture to GDI bitmap
                    var mapDest = bitmap.LockBits(boundsRect, ImageLockMode.WriteOnly, bitmap.PixelFormat);
                    var sourcePtr = mapSource.DataPointer;
                    var destPtr = mapDest.Scan0;
                    for (int y = 0; y < height/2; y++)
                    {
                        // Copy a single line 
                        Utilities.CopyMemory(destPtr, sourcePtr, width/2 * 4);

                        // Advance pointers
                        sourcePtr = IntPtr.Add(sourcePtr, mapSource.RowPitch);
                        destPtr = IntPtr.Add(destPtr, mapDest.Stride);
                    }

                    // Release source and dest locks
                    bitmap.UnlockBits(mapDest);
                    device.ImmediateContext.UnmapSubresource(stagingTexture, 0);

                    // Save the output
                    bitmap.Save(outputFileName);

                    // Capture done
                    captureDone = true;
                }

                screenResource.Dispose();
                duplicatedOutput.ReleaseFrame();

            }
            catch (SharpDXException e)
            {
                if (e.ResultCode.Code != SharpDX.DXGI.ResultCode.WaitTimeout.Result.Code)
                {
                    throw e;
                }
            }
        }

        // Display the texture using system associated viewer
        System.Diagnostics.Process.Start(Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, outputFileName)));

        // TODO: We should cleanp up all allocated COM objects here
    }
Rubbish answered 5/6, 2014 at 22:3 Comment(2)
Works like a charm, thanks man! One additional thing.. I'm also painting the mouse cursor, do you know any way to scale the cursor?Togetherness
Is it possible to crop the desktop capture using the same technique? my goal is to get a screenshot on a small part of the screen with fastest way possible way.Manwell
C
5

You need to take your original source surface in GPU memory and Draw() it on to a smaller surface. This involves simple vector/pixel shaders, which some folks with simple needs would rather bypass.

I would look to see if someone made a sprite lib for sharpdx. It should be a common "thing"...or using Direct2D (which is much more fun). Since D2D is just a user-mode library over D3D, it interops with D3D very easily.

I've never used SharpDx, but fFrom memory you would do something like this:

1.) Create an ID2D1Device, wrapping your existing DXGI Device (make sure your dxgi device creation flag has D3D11_CREATE_DEVICE_BGRA_SUPPORT)

2.) Get the ID2D1DeviceContext from your ID2D1Device

3.) Wrap your source and destination DXGI surfaces into D2D bitmaps with ID2D1DeviceContext::CreateBitmapFromDxgiSurface

4.) ID2D1DeviceContext::SetTarget of your destination surface

5.) BeginDraw, ID2D1DeviceContext::DrawBitmap, passing your source D2D bitmap. EndDraw

6.) Save your destination

Convocation answered 5/6, 2014 at 21:30 Comment(0)
K
0

Here is a pixelate example...

d2d_device_context_h()->BeginDraw();
d2d_device_context_h()->SetTarget(mp_ppBitmap1.Get());
D2D1_SIZE_F rtSize = mp_ppBitmap1->GetSize();
rtSize.height *= (1.0f / cbpx.iPixelsize.y);
rtSize.width *= (1.0f / cbpx.iPixelsize.x);
D2D1_RECT_F rtRect = { 0.0f, 0.0f, rtSize.width, rtSize.height };
D2D1_SIZE_F rsSize = mp_ppBitmap0->GetSize();
D2D1_RECT_F rsRect = { 0.0f, 0.0f, rsSize.width, rsSize.height };
d2d_device_context_h()->DrawBitmap(mp_ppBitmap0.Get(), &rtRect, 1.0f, 
D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, &rsRect);
d2d_device_context_h()->SetTarget(mp_ppBitmap0.Get());
d2d_device_context_h()->DrawBitmap(mp_ppBitmap1.Get(), &rsRect, 1.0f, 
D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, &rtRect);
d2d_device_context_h()->EndDraw();

Where iPixelsize.xy is the size of the "pixelated pixel", note that i just use linear interpolation when shrinking the bmp and NOT when i reenlarge. This will generate a pixelation effect.

Keenakeenan answered 28/4, 2017 at 12:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.