使用 SharpDX 调整 Texture2D(打印屏幕)的大小

Resize Texture2D (printscreen) with SharpDX

以下问题回答了如何将使用 SharpDX 拍摄的打印屏幕调整为二的幂 Resizing a DXGI Resource or Texture2D in SharpDX。我正在尝试通过可变数量调整打印屏幕的大小(例如原始大小的 80% - 不一定是二的幂)。现在我通过调整打印屏幕生成的位图的大小找到了“实现我的目标的方法”。我通过首先转换成 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;
    }

单击按钮后,上述代码将位图(包含打印屏幕)的大小调整为 2000x2000。但是,上面的代码很慢,大约需要200ms(不考虑fileread和filewrite的时间)。我使用 BitmapScaler 来调整大小。

有谁知道如何可变地调整 Resizing a DXGI Resource or Texture2D in SharpDX 问题产生的输出的大小,以便调整大小变得更快?我试图寻找文档以将 bitmapscaler 直接应用于已回答代码中的任何对象,但没有成功。

我已经上传了上面的代码可以找到a small Visual Studio Project which compiles

这是您的程序的重写和注释版本,它使用 DXGI 的输出复制从桌面获取视频帧,使用 Direct2D 使用任意比例调整它的大小,并使用 WIC 将它保存到 .jpeg 文件。

在使用 WIC 将图像保存到文件(流)之前,它只能在 GPU 中工作。在我的 PC 上,捕获和调整大小需要 10-15 毫秒,WIC 保存到文件需要 30-40 毫秒。

我没有使用我在评论中提到的 D2D 缩放效果,因为 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

请注意,我在 button1_Click 中创建和处置的一些对象可以在构造函数中创建(如工厂等)并重复使用。

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