Search code examples
c#directxsharpdx

Resize Texture2D (printscreen) with SharpDX


The following question answers how to resize a printscreen taken with SharpDX by a power of two Resizing a DXGI Resource or Texture2D in SharpDX. I'm trying to resize the printscreen by a variable amount (e.g. 80% of original size - not necessarily a power of two). Right now I found "a way to make my goal work" by resizing the bitmap generated by the printscreen. I achieve this by first converting into a WicImage:

    private void button1_Click(object sender, EventArgs e)
    {
        Stopwatch stopWatchInstance = Stopwatch.StartNew();
        //or Bitmap.save(new filestream)
        var stream = File.OpenRead("c:\\test\\pc.png");
        
        var test = DrawResizedImage(stream);
        stopWatchInstance.Stop();

        File.WriteAllBytes("c:\\test\\result.png", test.ToArray());

        int previousCalculationTimeServer = (int)(stopWatchInstance.ElapsedMilliseconds % Int32.MaxValue);
    }


    MemoryStream DrawResizedImage(Stream fileName)
    {
        ImagingFactory wic = new WIC.ImagingFactory();
        D2D.Factory d2d = new D2D.Factory();
        FormatConverter image = CreateWicImage(wic, fileName);
        var wicBitmap = new WIC.Bitmap(wic, image.Size.Width, image.Size.Height, WIC.PixelFormat.Format32bppPBGRA, WIC.BitmapCreateCacheOption.CacheOnDemand);
        var target = new D2D.WicRenderTarget(d2d, wicBitmap, new D2D.RenderTargetProperties());
        var bmpPicture = D2D.Bitmap.FromWicBitmap(target, image);

        target.BeginDraw();
        {
            target.DrawBitmap(bmpPicture, new SharpDX.RectangleF(0, 0, target.Size.Width, target.Size.Height), 1.0f, D2D.BitmapInterpolationMode.Linear);
        }
        target.EndDraw();

        var ms = new MemoryStream();
        SaveD2DBitmap(wic, wicBitmap, ms);
        return ms;
    }

    void SaveD2DBitmap(WIC.ImagingFactory wicFactory, WIC.Bitmap wicBitmap, Stream outputStream)
    {
        var encoder = new WIC.BitmapEncoder(wicFactory, WIC.ContainerFormatGuids.Png);
        encoder.Initialize(outputStream);
        var frame = new WIC.BitmapFrameEncode(encoder);

        frame.Initialize();
        frame.SetSize(wicBitmap.Size.Width, wicBitmap.Size.Height);

        var pixelFormat = wicBitmap.PixelFormat;
        frame.SetPixelFormat(ref pixelFormat);
        frame.WriteSource(wicBitmap);

        frame.Commit();
        encoder.Commit();
    }

    WIC.FormatConverter CreateWicImage(WIC.ImagingFactory wicFactory, Stream stream)
    {
        var decoder = new WIC.PngBitmapDecoder(wicFactory);

        var decodeStream = new WIC.WICStream(wicFactory, stream);
        decoder.Initialize(decodeStream, WIC.DecodeOptions.CacheOnLoad);
        var decodeFrame = decoder.GetFrame(0);

        var scaler = new BitmapScaler(wicFactory);
        scaler.Initialize(decodeFrame, 2000, 2000, SharpDX.WIC.BitmapInterpolationMode.Fant);
        var test = (BitmapSource)scaler;

        var converter = new WIC.FormatConverter(wicFactory);
        converter.Initialize(test, WIC.PixelFormat.Format32bppPBGRA);
        return converter;
    }

Upon clicking on button, the above code resizes a bitmap (containing the printscreen) to 2000x2000. However, the above code is very slow, it takes about 200ms (not taking into account the fileread and filewrite time). I use BitmapScaler to do the resizing.

Does anyone know how to variably resize the output produced from the Resizing a DXGI Resource or Texture2D in SharpDX question, so the resizing becomes much faster? I tried to look for documentation to apply bitmapscaler directly to any of the objects in the answered code, but didn't succeed.

I've uploaded the above code can be found as a small Visual Studio Project which compiles


Solution

  • Here is a rewritten and commented version of your program that gets a video frame from the desktop using DXGI's Output Duplication, resizes it using any ratio using Direct2D, and saves it to a .jpeg file using WIC.

    It works only in the GPU until the image is saved to a file (stream) using WIC. On my PC, I get something like 10-15 ms for the capture and resize, 30-40 ms for WIC save to file.

    I've not used the D2D Scale effect I talked about in my comment because the ID2D1DeviceContext::DrawBitmap method can do resize that with various interpolation factors, without using any effect. But you can use the same code to apply Hardware accelerated effects.

    Note some objects I create and dispose in button1_Click could be created in the constructor (like factories, etc.) and reused.

    using System;
    using System.Windows.Forms;
    using System.IO;
    using DXGI = SharpDX.DXGI;
    using D3D11 = SharpDX.Direct3D11;
    using D2D = SharpDX.Direct2D1;
    using WIC = SharpDX.WIC;
    using Interop = SharpDX.Mathematics.Interop;
    
    namespace WindowsFormsApp1
    {
        public partial class Form1 : Form
        {
            private readonly D3D11.Device _device;
            private readonly DXGI.OutputDuplication _outputDuplication;
    
            public Form1()
            {
                InitializeComponent();
    
                var adapterIndex = 0; // adapter index
                var outputIndex = 0; // output index
    
                using (var dxgiFactory = new DXGI.Factory1())
                using (var dxgiAdapter = dxgiFactory.GetAdapter1(adapterIndex))
                using (var output = dxgiAdapter.GetOutput(outputIndex))
                using (var dxgiOutput = output.QueryInterface<DXGI.Output1>())
                {
                    _device = new D3D11.Device(dxgiAdapter,
    #if DEBUG
                        D3D11.DeviceCreationFlags.Debug |
    #endif
                        D3D11.DeviceCreationFlags.BgraSupport); // for D2D support
    
                    _outputDuplication = dxgiOutput.DuplicateOutput(_device);
                }
            }
    
            protected override void Dispose(bool disposing) // remove from Designer.cs
            {
                if (disposing && components != null)
                {
                    components.Dispose();
                    _outputDuplication?.Dispose();
                    _device?.Dispose();
                }
                base.Dispose(disposing);
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                var ratio = 0.8; // resize ratio
    
                using (var dxgiDevice = _device.QueryInterface<DXGI.Device>())
                using (var d2dFactory = new D2D.Factory1())
                using (var d2dDevice = new D2D.Device(d2dFactory, dxgiDevice))
                {
                    // acquire frame
                    _outputDuplication.AcquireNextFrame(10000, out var _, out var frame);
                    using (frame)
                    {
                        // get DXGI surface/bitmap from resource
                        using (var frameDc = new D2D.DeviceContext(d2dDevice, D2D.DeviceContextOptions.None))
                        using (var frameSurface = frame.QueryInterface<DXGI.Surface>())
                        using (var frameBitmap = new D2D.Bitmap1(frameDc, frameSurface))
                        {
                            // create a GPU resized texture/surface/bitmap
                            var desc = new D3D11.Texture2DDescription
                            {
                                CpuAccessFlags = D3D11.CpuAccessFlags.None, // only GPU
                                BindFlags = D3D11.BindFlags.RenderTarget, // to use D2D
                                Format = DXGI.Format.B8G8R8A8_UNorm,
                                Width = (int)(frameSurface.Description.Width * ratio),
                                Height = (int)(frameSurface.Description.Height * ratio),
                                OptionFlags = D3D11.ResourceOptionFlags.None,
                                MipLevels = 1,
                                ArraySize = 1,
                                SampleDescription = { Count = 1, Quality = 0 },
                                Usage = D3D11.ResourceUsage.Default
                            };
                            using (var texture = new D3D11.Texture2D(_device, desc))
                            using (var textureDc = new D2D.DeviceContext(d2dDevice, D2D.DeviceContextOptions.None)) // create a D2D device context
                            using (var textureSurface = texture.QueryInterface<DXGI.Surface>()) // this texture is a DXGI surface
                            using (var textureBitmap = new D2D.Bitmap1(textureDc, textureSurface)) // we can create a GPU bitmap on a DXGI surface
                            {
                                // associate the DC with the GPU texture/surface/bitmap
                                textureDc.Target = textureBitmap;
    
                                // this is were we draw on the GPU texture/surface
                                textureDc.BeginDraw();
    
                                // this will automatically resize
                                textureDc.DrawBitmap(
                                    frameBitmap,
                                    new Interop.RawRectangleF(0, 0, desc.Width, desc.Height),
                                    1,
                                    D2D.InterpolationMode.HighQualityCubic, // change this for quality vs speed
                                    null,
                                    null);
    
                                // commit draw
                                textureDc.EndDraw();
    
                                // now save the file, create a WIC (jpeg) encoder
                                using (var file = File.OpenWrite("test.jpg"))
                                using (var wic = new WIC.ImagingFactory2())
                                using (var jpegEncoder = new WIC.BitmapEncoder(wic, WIC.ContainerFormatGuids.Jpeg))
                                {
                                    jpegEncoder.Initialize(file);
                                    using (var jpegFrame = new WIC.BitmapFrameEncode(jpegEncoder))
                                    {
                                        jpegFrame.Initialize();
    
                                        // here we use the ImageEncoder (IWICImageEncoder)
                                        // that can write any D2D bitmap directly
                                        using (var imageEncoder = new WIC.ImageEncoder(wic, d2dDevice))
                                        {
                                            imageEncoder.WriteFrame(textureBitmap, jpegFrame, new WIC.ImageParameters(
                                                new D2D.PixelFormat(desc.Format, D2D.AlphaMode.Premultiplied),
                                                textureDc.DotsPerInch.Width,
                                                textureDc.DotsPerInch.Height,
                                                0,
                                                0,
                                                desc.Width,
                                                desc.Height));
                                        }
    
                                        // commit
                                        jpegFrame.Commit();
                                        jpegEncoder.Commit();
                                    }
    
                                }
                            }
                        }
                    }
                    _outputDuplication.ReleaseFrame();
                }
            }
        }
    }