2014-06-05 23 views
7

Desidero ridimensionare una schermata acquisita utilizzando l'API Desktop Duplication in SharpDX. Sto usando il Screen Capture sample code from the SharpDX Samples repository, la parte relativa segue :.Ridimensionamento di una risorsa DXGI o Texture2D in SharpDX

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... 

desidero ridimensionare l'immagine molto più piccola della dimensione effettiva dello schermo prima di elaborarlo come matrice di byte. Non ho bisogno di salvare l'immagine, basta prendere i byte. Vorrei farlo in modo relativamente rapido ed efficiente (ad esempio facendo leva sulla GPU se possibile).

Non riesco a ridimensionare durante CopyResource, poiché le dimensioni di output devono essere uguali alle dimensioni di input. Posso eseguire un'altra copia dal mio screenTexture2D in scala? In che modo posso ridimensionare la risorsa: utilizzo una catena di scambio, una trasformazione di matrice o qualcos'altro?

risposta

5

Se si sta bene il ridimensionamento ad una potenza di due dallo schermo, è possibile farlo da:

  • creare una texture più piccola con RenderTarget/ShaderResource utilizzo e opzioni GenerateMipMaps, stessa dimensione dello schermo, mipcount> 1 (2 per avere dimensioni/2, 3 per avere /4... etc).
  • Copia il primo mipmap dello schermo consistenza alla struttura più piccola
  • DeviceContext.GenerateMipMaps sul minore consistenza
  • Copiare la mimap selezionato della texture più piccolo (1:/2, 2:. /4...etc) alla texture messa in scena (che dovrebbe anche essere dichiarata più piccolo, cioè stesse dimensioni del mipmap che sta per essere usato)

un hack rapido sul codice originale per generare una texture/2 sarebbe come questo:

[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 
    } 
6

È necessario portare la superficie della sorgente originale nella memoria della GPU e disegnare() su una superficie più piccola. Ciò comporta semplici shader vettoriali/pixel, che alcune persone con esigenze semplici preferirebbero ignorare.

Vorrei vedere se qualcuno ha realizzato una lib di sprite per sharpdx. Dovrebbe essere una "cosa" comune ... o usare Direct2D (che è molto più divertente). Poiché D2D è solo una libreria in modalità utente su D3D, interpone molto facilmente con D3D.

Non ho mai usato SharpDx, ma ffrom memoria si dovrebbe fare qualcosa di simile:

1.) Creare un ID2D1Device, avvolgendo il dispositivo DXGI esistente (assicuratevi che il vostro dispositivo DXGI creazione bandiera ha D3D11_CREATE_DEVICE_BGRA_SUPPORT)

2.) Prendi l'ID2D1DeviceContext dal ID2D1Device

3.) Avvolgere le superfici di origine e di destinazione DXGI in bitmap D2D con ID2D1DeviceContext :: CreateBitmapFromDxgiSurface

4.) ID2D1DeviceContext :: SetTarget della vostra superficie di destinazione

5.) BeginDraw, ID2D1DeviceContext :: DrawBitmap, passando il tuo bitmap di origine D2D. EndDraw

6.) Salva destinazione

0

Ecco un esempio di pixel ...

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

Dove iPixelsize.xy è la dimensione del "pixel pixelated", nota che uso appena interpolazione lineare quando la contrazione BMP e non quando reenlarge. Questo genererà un effetto pixel.