Developers using the ID2D1Bitmap::CopyFromBitmap and ID2D1Bitmap::CopyFromRenderTarget APIs may notice problems when the source and destination rectangles are of different sizes. In particular, the source rectangle is incorrectly clipped against the destination bounds (instead of against the source bounds). The destination rectangle is correctly clipped against the destination bounds.
Terminology: Since the problem in each API is very similar, it is helpful to talk about both problems at the same time. However, in order to do that, we need a common word to refer to either a bitmap or a render target. This post will use the term “Surface”.
There are two possible manifestations of this problem:
1. Access violation (crash)
This happens when you pass a source rectangle that exceeds the bounds of the source surface but the destination surface is smaller than the source surface. The clipping will be insufficient and may result in a crash. In this case applications can work around this problem by sanitizing their inputs – don’t pass source rectangles that exceed the bounds of the source surface.
This happens when you pass a source rectangle that doesn’t exceed the bounds of the source surface but does exceed the bounds of the destination surface. The clipping will be overly zealous and less data will be copied than requested. In this case there are three possible workarounds:
a) Use D3D
Since the problem is in D2D customers using D3D and D2D together can call the corresponding APIs on the D3D interfaces. This is the option with the best performance, but it may be cumbersome to code in some scenarios. It requires that all associated D2D bitmaps to have been created with ID2D1RenderTarget::CreateSharedBitmap (IDXGISurface variant) and all associated D2D render targets to have been created with ID2D1Factory::CreateDxgiSurfaceRenderTarget. For example:
ID2D1RenderTarget *pRenderTarget = NULL;
// <Snip> Your render target code goes here.
ID2D1Bitmap *pBitmap = NULL;
// <Snip> Your DXGI surface manipulation code goes here.
These two APIs create D2D objects that wrap D3D textures. An application can hold on to the underlying D3D textures and call the ID3D10Device::CopySubresourceRegion API instead of the ID3D1Bitmap::CopyFromBitmap/ID2D1Bitmap::CopyFromRenderTarget APIs. If you choose this route, make sure you call EndDraw on all associated render targets first (including render targets against which drawing operations involving the bitmaps have been issued).
b) Use an intermediate bitmap
This option is flexible, but it is costly from a performance standpoint (in terms of both memory footprint and speed). If you are attempting to copy from an ID2D1HwndRenderTarget, this is your only option. Create an intermediate bitmap the same size as the source surface. Call CopyFromBitmap / CopyFromRenderTarget to copy the desired region to the upper-left corner of the intermediate bitmap. Then copy from the upper-left corner of the intermediate bitmap to the destination. Even though this requires copying between surfaces of different sizes as the final step, this copy is from the upper-left corner, which ensures that the source rectangle will be within the destination bounds, so it will not be affected by the clipping.
c) Use a DrawBitmap call instead of CopyFromBitmap/CopyFromRenderTarget
This option is not as flexible as option 2, but in some cases it will be less costly from a performance standpoint. The DrawBitmap API allows you to specify both a source and a destination rectangle so you can use it instead of CopyFromBitmap/CopyFromRenderTarget, provided that the destination is a render target and that the source is a bitmap. You can create a surface that is both a render target and a bitmap using the ID2D1RenderTarget::CreateCompatibleRenderTarget API. You will not be able to use this option if you wish to copy from an ID2D1HwndRenderTarget. It is not possible to create a surface that is both an ID2D1HwndRenderTarget and an ID2D1Bitmap.