如何将选择从 Direct2DCanvas 复制到剪贴板?

How to copy a selection from Direct2DCanvas to clipboard?

我制作了一个小程序来在 D2DBox(使用 Direct2DCanvas 和 RenderTarget)上绘制几何图形,我需要能够从它复制一个矩形到剪贴板。试过这个,但我坚持使用源句柄参数:

bm := TBitmap.Create;
try
  bm.SetSize(maxx-minx, maxy-miny);
  BitBlt(bm.Canvas.Handle, minx, miny, maxx, maxy, ??? , 0, 0, SRCCOPY);
  Clipboard.Assign(bm);
finally
  bm.Free;
end;

知道从哪里得到句柄吗?或者整个事情以不同的方式完成?谢谢!

BitBlt() 需要一个 GDI HDC 来复制,但是 TDirect2DCanvas 没有自己的 HDC,它不能直接 渲染到屏幕外 HDC/TCanvas,例如 TBitmap.Canvas,根据其 documentation:

TDirect2DCanvas will only work for on-screen device contexts. You cannot use the TDirect2DCanvas to draw on a printer device context, for example.

并且您不能关联自定义 RenderTarget(例如使用 ID2D1Factory.CreateDCRenderTarget() and ID2D1DCRenderTarget.BindDC()) since the TDirect2DCanvas.RenderTarget 属性 创建的是只读的。

所以,您可能需要走很长的路才能得到您想要的。根据我在 Direct2d Desktop printing C++ 中找到的代码,它演示了将任意 ID2D1RenderTarget 复制到任意 HDC,您可以尝试以下操作:

  • 使用目标的 CreateBitmap() 方法之一创建一个绑定到 canvas 当前 RenderTarget 的 Direct2D ID2D1Bitmap

  • 使用位图的 CopyFromRenderTarget() 方法将 canvas 中的像素复制到位图中。

  • 创建一个 IWICBitmap (you can probably use the VCL's TWICImage for this) and render the Direct2D bitmap to it using one of the ID2D1Factory.CreateWicBitmapRenderTarget() methods with one of the ID2D1RenderTarget.DrawBitmap() 方法。

  • 创建 GDI DIB 位图并使用 WIC 位图的 CopyPixels() 方法将 IWICBitmap 渲染到它。

  • 最后,您可以根据需要使用 DIB,例如 select/copy 到您最终的 HDC,或者您可以直接将其内容放在使用 CF_DIB 格式的剪贴板。

这是代码(抱歉,它是用 C++ 编写的,我不会将其翻译成 Delphi):

void Direct2DRender::RenderToDC(HDC hDC, UINT uiWidth, UINT uiHeight)
{
  HRESULT hr = S_OK;

  IWICImagingFactory *pImageFactory = WICImagingFactory::GetInstance().GetFactory();

  CComPtr<IWICBitmap> wicBitmap;
  hr = pImageFactory->CreateBitmap( uiWidth, uiHeight, GUID_WICPixelFormat32bppBGR, WICBitmapCacheOnLoad, &wicBitmap);

  D2D1_SIZE_U bitmapPixelSize = D2D1::SizeU( uiWidth, uiHeight);

  float dpiX, dpiY;
  m_pRenderTarget->GetDpi( &dpiX, &dpiY);

  CComPtr<ID2D1Bitmap> d2dBitmap;
  hr = m_pRenderTarget->CreateBitmap( bitmapPixelSize, D2D1::BitmapProperties(
    D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE),
    dpiX, dpiY), &d2dBitmap );

  D2D1_POINT_2U dest = D2D1::Point2U(0,0);
  D2D1_RECT_U src = D2D1::RectU(0, 0, uiWidth, uiHeight);

  hr = d2dBitmap->CopyFromRenderTarget(&dest, m_pRenderTarget, &src);

  D2D1_RENDER_TARGET_PROPERTIES rtProps = D2D1::RenderTargetProperties();
    rtProps.pixelFormat = D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE);
    rtProps.type = D2D1_RENDER_TARGET_TYPE_DEFAULT;
    rtProps.usage = D2D1_RENDER_TARGET_USAGE_NONE;

  CComPtr<ID2D1RenderTarget> wicRenderTarget;
  hr = m_pDirect2dFactory->CreateWicBitmapRenderTarget( wicBitmap, rtProps, &wicRenderTarget);

  wicRenderTarget->BeginDraw();
  wicRenderTarget->DrawBitmap(d2dBitmap);
  hr = wicRenderTarget->EndDraw();

  // Render the image to a GDI device context
  HBITMAP hDIBBitmap = NULL;
  try
  {
    // Get a DC for the full screen
    HDC hdcScreen = GetDC(NULL);
    if (!hdcScreen)
      throw 1;

    BITMAPINFO bminfo;
    ZeroMemory(&bminfo, sizeof(bminfo));
    bminfo.bmiHeader.biSize         = sizeof(BITMAPINFOHEADER);
    bminfo.bmiHeader.biWidth        = uiWidth;
    bminfo.bmiHeader.biHeight       = -(LONG)uiHeight;
    bminfo.bmiHeader.biPlanes       = 1;
    bminfo.bmiHeader.biBitCount     = 32;
    bminfo.bmiHeader.biCompression  = BI_RGB;     

    void* pvImageBits = nullptr;  // Freed with DeleteObject(hDIBBitmap)
    hDIBBitmap = CreateDIBSection(hdcScreen, &bminfo, DIB_RGB_COLORS, &pvImageBits, NULL, 0);
    if (!hDIBBitmap)
      throw 2;

    ReleaseDC(NULL, hdcScreen);

    // Calculate the number of bytes in 1 scanline
    UINT nStride = DIB_WIDTHBYTES(uiWidth * 32);
    // Calculate the total size of the image
    UINT nImage = nStride * uiHeight;
    // Copy the pixels to the DIB section
    hr = wicBitmap->CopyPixels(nullptr, nStride, nImage, reinterpret_cast<BYTE*>(pvImageBits));

    // Copy the bitmap to the target device context
    ::SetDIBitsToDevice(hDC, 0, 0, uiWidth, uiHeight, 0, 0, 0, uiHeight, pvImageBits, &bminfo, DIB_RGB_COLORS);

    DeleteObject(hDIBBitmap);
  }
  catch (...)
  {
    if (hDIBBitmap)
      DeleteObject(hDIBBitmap);
    // Rethrow the exception, so the client code can handle it
    throw;
  }
}

在同一个讨论中,描述了另一种选择:

  • Create a DIB section, and select it into a DC

  • Create a DC render target

  • Bind the render target to the DC that corresponds to the DIB section

  • Draw using Direct2D. After calling EndDraw the DIB contains what was rendered.

  • The final step is to draw the dib where you need it.

因此,请尝试将您的绘图代码移动到它自己的函数中,该函数将 ID2D1RenderTarget 作为输入并根据需要在其上绘制。然后,当你想在剪贴板上放置位图时,你可以创建一个基于 HDCRenderTarget,当你想在你的 D2DBox 上绘图时使用 TDirect2DCanvas.RenderTarget