I made a small program to draw geometry figures on a D2DBox (with Direct2DCanvas and RenderTarget) and I need to be able to copy a rectangle from it to the clipboard. Tried this, but I'm stuck with the source handle parameter:
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;
Any idea where to get a handle from? Or the whole thing is done a different way? Thanx!
BitBlt()
requires a GDI HDC
to copy from, but TDirect2DCanvas
does not have an HDC
of its own, and it cannot directly render to an off-screen HDC
/TCanvas
, such as TBitmap.Canvas
, per its documentation:
TDirect2DCanvas will only work for on-screen device contexts. You cannot use the TDirect2DCanvas to draw on a printer device context, for example.
And you can't associate a custom RenderTarget
(such as one created with ID2D1Factory.CreateDCRenderTarget()
and ID2D1DCRenderTarget.BindDC()
) since the TDirect2DCanvas.RenderTarget property is read-only.
So, you will likely have to go a long way to get what you want. Based on code I found in Direct2d Desktop printing C++, which demonstrates copying an arbitrary ID2D1RenderTarget
to an arbitrary HDC
, you can try the following:
Create a Direct2D ID2D1Bitmap
that is bound to the canvas's current RenderTarget
using one of the target's CreateBitmap()
methods.
Copy pixels from the canvas into the bitmap using the bitmap's CopyFromRenderTarget()
method.
Create an 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()
methods.
Create a GDI DIB
bitmap and render the IWICBitmap
to it using the WIC bitmap's CopyPixels()
method.
Finally, you use the DIB
however you needed, such as select/copy it to your final HDC
, or you can simply place its contents directly on the clipboard using the CF_DIB
format.
Here is the code (sorry, it is in C++, I'm not going to translate it to 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; } }
In that same discussion, another alternative is described:
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.
So, try moving your drawing code to its own function that takes an ID2D1RenderTarget
as input and draws on it as needed. Then, you can create an HDC
-based RenderTarget
when you want to place a bitmap on the clipboard, and use TDirect2DCanvas.RenderTarget
when you want to draw on your D2DBox
.