1
votes

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

1
If you want to resize a GPU texture, don't get back to the CPU (WIC is 100% CPU) too early. Use a D2D device context from the D3D11 device, and just after you got your Texture2D, draw the GPU bitmap with the D2D Scale effect: docs.microsoft.com/en-us/windows/win32/direct2d/…, and use WIC only at the end if you want a file. (avoid GDI+ System.Drawing, avoid MemoryStream)Simon Mourier
@SimonMourier Thanks for your input! Would it please be possible to elaborate this a bit with some of the API's I would need to use (in succession)? With the best intentions of me trying to put in the effort, I'm pretty stuck due to my limited knowledge of the Directx worlduser3231622
The API is simply Direct2D, the answer here also helps: stackoverflow.com/a/43679968/403671 If you have a small full reproducing project, I can have a look.Simon Mourier
@SimonMourier Thanks for your patience. I uploaded the code as a small project which includes the SharpDX DLLs: github.com/pinguincoder/directxresize/blob/main/directxResize/…user3231622

1 Answers

2
votes

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