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.
Related
I'm using the Embarcadero RAD Studio C++ builder XE7 compiler. In an application project, I'm using the both Windows GDI and GDI+ to draw on several device contexts.
My drawing content is something like that:
On the above sample the text background and the user picture are drawn with GDI+. The user picture is also clipped with a rounded path. All the other items (the text and the emojis) are drawn with the GDI.
When I draw to the screen DC, all works fine.
Now I want to draw on a printer device context. Whichever I use for my tests is the new "Export to PDF" printer device available in Windows 10. I prepare my device context to draw on an A4 viewport this way:
HDC GetPrinterDC(HWND hWnd) const
{
// initialize the print dialog structure, set PD_RETURNDC to return a printer device context
::PRINTDLG pd = {0};
pd.lStructSize = sizeof(pd);
pd.hwndOwner = hWnd;
pd.Flags = PD_RETURNDC;
// get the printer DC to use
::PrintDlg(&pd);
return pd.hDC;
}
...
void Print()
{
HDC hDC = NULL;
try
{
hDC = GetPrinterDC(Application->Handle);
const TSize srcPage(793, 1123);
const TSize dstPage(::GetDeviceCaps(hDC, PHYSICALWIDTH), ::GetDeviceCaps(hDC, PHYSICALHEIGHT));
const TSize pageMargins(::GetDeviceCaps(hDC, PHYSICALOFFSETX), ::GetDeviceCaps(hDC, PHYSICALOFFSETY));
::SetMapMode(hDC, MM_ISOTROPIC);
::SetWindowExtEx(hDC, srcPage.Width, srcPage.Height, NULL);
::SetViewportExtEx(hDC, dstPage.Width, dstPage.Height, NULL);
::SetViewportOrgEx(hDC, -pageMargins.Width, -pageMargins.Height, NULL);
::DOCINFO di = {sizeof(::DOCINFO), config.m_FormattedTitle.c_str()};
::StartDoc (hDC, &di);
// ... the draw function is executed here ...
::EndDoc(hDC);
return true;
}
__finally
{
if (hDC)
::DeleteDC(hDC);
}
}
The draw function executed between the StartDoc() and EndDoc() functions is exactly the same as whichever I use to draw on the screen. The only difference is that I added a global clipping rect on my whole page, to avoid the drawing to overlaps on the page margins when the size is too big, e.g. when I repeat the above drawing several times under the first one. (This is experimental, later I will add a page cutting process, but this is not the question for now)
Here are my clipping functions:
int Clip(const TRect& rect, HDC hDC)
{
// save current device context state
int savedDC = ::SaveDC(hDC);
HRGN pClipRegion = NULL;
try
{
// reset any previous clip region
::SelectClipRgn(hDC, NULL);
// create clip region
pClipRegion = ::CreateRectRgn(rect.Left, rect.Top, rect.Right, rect.Bottom);
// select new canvas clip region
if (::SelectClipRgn(hDC, pClipRegion) == ERROR)
{
DWORD error = ::GetLastError();
::OutputDebugString(L"Unable to select clip region - error - " << ::IntToStr(error));
}
}
__finally
{
// delete clip region (it was copied internally by the SelectClipRgn())
if (pClipRegion)
::DeleteObject(pClipRegion);
}
return savedDC;
}
void ReleaseClip(int savedDC, HDC hDC)
{
if (!savedDC)
return;
if (!hDC)
return;
// restore previously saved device context
::RestoreDC(hDC, savedDC);
}
As mentioned above, I expected a clipping around my page. However the result is just a blank page. If I bypass the clipping functions, all is printed correctly, except that the draw may overlap on the page margins. On the other hands, if I apply the clipping on an arbitrary rect on my screen, all works fine.
What I'm doing wrong with my clipping? Why the page is completely broken when I enables it?
So I found what was the issue. Niki was close to the solution. The clipping functions seem always applied to the page in pixels, ignoring the coordinate system and the units defined by the viewport.
In my case, the values passed to the CreateRectRgn() function were wrong, because they remained untransformed by the viewport, although the clipping was applied after the viewport was set in the device context.
This turned the identification of the issue difficult, because the clipping appeared as transformed while the code was read, as it was applied after the viewport, just before the drawing was processed.
I don't know if this is a GDI bug or a wished behavior, but unfortunately I never seen this detail mentioned in all the documents I read about the clipping. Although it seems to me important to know that the clipping isn't affected by the viewport.
I use the direct 2d API to draw text in the GDI dc correctly,
but When I use the same code to draw text in the printer DC, it failed,
The printer hdc is create as below,
hdc = CreateDC("WINSPOOL", printerName, "", pdevMode);
The Direct 2d draw text code snippet is as below
if (p_d2dFactory == NULL) {
hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &p_d2dFactory);
}
if (SUCCEEDED(hr) && p_dcTarget == NULL) {
hr = p_d2dFactory->CreateDCRenderTarget(&props, &p_dcTarget);
}
if (SUCCEEDED(hr)) {
hr = p_dcTarget->BindDC(hdc, &rc);
}
if (SUCCEEDED(hr)) {
p_dcTarget->BeginDraw();
p_dcTarget->Clear(NULL);
p_dcTarget->DrawTextLayout(origin, g_pTextLayout,p_dbrush);
hr= p_dcTarget->EndDraw();
}
Unfortunately, the p_dcTarget can not bind the hdc correctly, so there is nothing printed out,
but if the hdc is the GDI dc to draw text in the window, it will bind successfully and then draw the correct text out.
Is there anything different when binding the printer DC?
Any suggestion for this trouble? Thanks.
According to this discussion on MSDN, you can't print directly from Direct2D to a printer DC. Instead, you'll need to render to an in-memory bitmap then copy that bitmap to the printer with BitBlt or StretchBlt.
The MSDN documentation on GDI and Direct2D interoperability points out that, even if Direct2D did work with printer DCs, it would be doing this internally:
When you use an ID2D1DCRenderTarget, it renders Direct2D content to an internal bitmap, and then renders the bitmap to the DC with GDI.
I found many examples about how to scale an image in Windows Forms, but at this case I'm using an array of bytes in a Windows Store application. This is the snippet code what I'm using.
// Now that you have the raw bytes, create a Image Decoder
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(fileStream);
// Get the first frame from the decoder because we are picking an image
BitmapFrame frame = await decoder.GetFrameAsync(0);
// Convert the frame into pixels
PixelDataProvider pixelProvider = await frame.GetPixelDataAsync();
// Convert pixels into byte array
srcPixels = pixelProvider.DetachPixelData();
wid = (int)frame.PixelWidth;
hgt =(int)frame.PixelHeight;
// Create an in memory WriteableBitmap of the same size
bitmap = new WriteableBitmap(wid, hgt);
Stream pixelStream = bitmap.PixelBuffer.AsStream();
pixelStream.Seek(0, SeekOrigin.Begin);
// Push the pixels from the original file into the in-memory bitmap
pixelStream.Write(srcPixels, 0, (int)srcPixels.Length);
bitmap.Invalidate();
At this case, it is just creating a copy of the stream. I don't know how to manipulate the byte array to reduce it to the half width and height.
If you look at the MSDN documentation for GetPixelDataAsync, you can see that it has an overload that allows you to specify a BitmapTransform to be applied during the operation.
So you can do this in your example code, something like this:
// decode a frame (as you do now)
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(fileStream);
BitmapFrame frame = await decoder.GetFrameAsync(0);
// calculate required scaled size
uint newWidth = frame.PixelWidth / 2;
uint newHeight = frame.PixelHeight / 2;
// convert (and resize) the frame into pixels
PixelDataProvider pixelProvider =
await frame.GetPixelDataAsync(
BitmapPixelFormat.Rgba8,
BitmapAlphaMode.Straight,
new BitmapTransform() { ScaledWidth = newWidth, ScaledHeight = newHeight},
ExifOrientationMode.RespectExifOrientation,
ColorManagementMode.DoNotColorManage);
Now, you can call DetachPixelData as in your original code, but this will give you the resized image instead of the full sized image.
srcPixels = pixelProvider.DetachPixelData();
// create an in memory WriteableBitmap of the scaled size
bitmap = new WriteableBitmap(newWidth, newHeight);
Stream pixelStream = bitmap.PixelBuffer.AsStream();
pixelStream.Seek(0, SeekOrigin.Begin);
// push the pixels from the original file into the in-memory bitmap
pixelStream.Write(srcPixels, 0, (int)srcPixels.Length);
bitmap.Invalidate();
I am trying to use DirectX to create and paint into a bitmap, and then later use that bitmap to paint to the screen. I have the following test code which I have pieced together from a number of sources. I seem to be able to create the bitmap without errors, and I try and fill it with red. However, when I use the bitmap to paint to the screen I just get a black rectangle. Can anyone tell me what I might be doing wrong? Note that this is running as a Windows 8 Metro app, if that makes a difference.
Note that the formatter seems to be removing my angle brackets and I don't how stop it from doing that. The 'device' variable is of class ID3D11Device, the 'context' variable is ID3D11DeviceContext, the d2dContext variable is ID2D1DeviceContext, the dgixDevice variable is IDXGIDevice, and the 'd2dDevice' is ID2D1Device.
void PaintTestBitmap(GRectF& rect)
{
HRESULT hr;
UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
D3D_FEATURE_LEVEL featureLevels[] =
{
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_9_2,
D3D_FEATURE_LEVEL_9_1
};
ComPtr<ID3D11Device> device;
ComPtr<ID3D11DeviceContext> context;
ComPtr<ID2D1DeviceContext> d2dContext;
hr = D3D11CreateDevice( NULL,
D3D_DRIVER_TYPE_HARDWARE,
NULL,
creationFlags,
featureLevels,
ARRAYSIZE(featureLevels),
D3D11_SDK_VERSION,
&device,
NULL,
&context);
if (SUCCEEDED(hr))
{
ComPtr<IDXGIDevice> dxgiDevice;
device.As(&dxgiDevice);
// create D2D device context
ComPtr<ID2D1Device> d2dDevice;
hr = D2D1CreateDevice(dxgiDevice.Get(), NULL, &d2dDevice);
if (SUCCEEDED(hr))
{
hr = d2dDevice->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &d2dContext);
if (SUCCEEDED(hr))
{
ID2D1Bitmap1 *pBitmap;
D2D1_SIZE_U size = { rect.m_width, rect.m_height };
D2D1_BITMAP_PROPERTIES1 properties = {{ DXGI_FORMAT_R8G8B8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED }, 96, 96, D2D1_BITMAP_OPTIONS_TARGET, 0 };
hr = d2dContext->CreateBitmap(size, (const void *) 0, 0, &properties, &pBitmap);
if (SUCCEEDED(hr))
{
d2dContext->SetTarget(pBitmap);
d2dContext->BeginDraw();
d2dContext->Clear(D2D1::ColorF(D2D1::ColorF::Red)); // Clear the bitmap to Red
d2dContext->EndDraw();
// If I now use pBitmap to paint to the screen, I get a black rectange
// rather than the red one I was expecting
}
}
}
}
}
Note that I am fairly new to both DirectX and Metro style apps.
Thanks
what you're probably forgetting is that you dont draw your bitmap on your context.
what you can try is
hr = d2dContext->CreateBitmap(size, (const void *) 0, 0, &properties, &pBitmap);
if (SUCCEEDED(hr))
{
ID2D1Image *pImage = nullptr;
d2dContext->GetTarget(&image);
d2dContext->SetTarget(pBitmap);
d2dContext->BeginDraw();
d2dContext->Clear(D2D1::ColorF(D2D1::ColorF::Red)); // Clear the bitmap to Red
d2dContext->SetTarget(pImage);
d2dContext->DrawBitmap(pBitmap);
d2dContext->EndDraw();
}
what you do is store your rendertarget and then draw your bitmap and at the end you set your context to the right render target and let it draw your bitmap on it
Can anybody help with converting an SDL_Surface object, a texture loaded from a file, into an IDirect3DTexture9 object.
I honestly don't know why you would ever want to do this. It sounds like a truly horrible idea for a variety of reasons, so please tell us why you want to do this so we can convince you not to ;).
In the meanwhile, a quick overview of how you'd go about it:
IDirect3DTexture9* pTex = NULL;
HRESULT hr = S_OK;
hr = m_d3dDevice->CreateTexture(
surface->w,
surface->h,
1,
usage,
format,
D3DPOOL_MANAGED,
&pTex,
NULL);
This creates the actual texture with the size and format of the SDL_Surface. You'll have to fill in the usage on your own, depending on how you want to use it (see D3DUSAGE). You'll also have to figure out the format on your own - you can't directly map a SDL_PixelFormat to a D3DFORMAT. This won't be easy, unless you know exactly what pixel format your SDL_Surface is.
Now, you need to write the data into the texture. You can't use straight memcpy here, since the SDL_Surface and the actual texture may have different strides. Here's some untested code that may do this for you:
HRESULT hr;
D3DLOCKED_RECT lockedRect;
// lock the texture, so that we can write into it
// Note: if you used D3DUSAGE_DYNAMIC above, you should
// use D3DLOCK_DISCARD as the flags parameter instead of 0.
hr = pTex->LockRect(0, &lockedRect, NULL, 0);
if(SUCCEEDED(hr))
{
// use char pointers here for byte indexing
char* src = (char*) surface->pixels;
char* dst = (char*) lockedRect->pBits;
size_t numRows = surface->h;
size_t rowSize = surface->w * surface->format->BytesPerPixel;
// for each row...
while(numRows--)
{
// copy the row
memcpy(dst, src, rowSize);
// use the given pitch parameters to advance to the next
// row (since these may not equal rowSize)
src += surface->pitch;
dst += lockedRect->Pitch;
}
// don't forget this, or D3D won't like you ;)
hr = pTex->UnlockRect(0);
}