NOTE: adding this after the proposed reward in addition to my original answer
For starters, there is no need for a 400x400 background rectangle because you only show a 300x300 bitmap, so here is the first change:
ctx.DrawRectangle(Brushes.White, new Pen(Brushes.White, 10), new System.Windows.Rect(0, 0, 300, 300));
With this change in place, the conclusion will be exactly the same, but it will simplify the explanation.
Where possible and logical, WPF uses DIP (device-independent pixels) as units, not pixels. When you do this:
<Rectangle Width="100" Height="100"/>
Ultimately, you will not have a Rectangle
100x100 physical pixels. If your device has more (or less) 96 pixels per physical inch, then in the end you will have a different number of physical pixels. I think 96 ppi is a kind of industry standard. Modern devices, such as smartphones and tablets, have much more pixels per physical inch. If WPF used physical pixels as a unit of measurement, then the aforementioned Rectangle
will display less on such a device.
Now for rendering a bitmap (or JPEG, PNG, GIF, etc.), you must use pixels using the device, since this is a rasterized format (not a vector format). And this is what you specify when calling the RenderTargetBitmap
constructor. You say you want the resulting bitmap to be 300x300 physical pixels with DPI 40. Since the source has DPI 96 (assuming your monitor is an industry standard) and the target has DPI 40, it must compress the source to set the target . Therefore, the effect is a shrunken image in your bitmap rendering.
Now, what you really want to do is make sure the source DPI and destination DPI are the same. This is not as simple as hard coding 96, because, as discussed, this is just a standard - the source can actually have more or less DPI than that. Unfortunately, WPF does not provide a good way to get DPI, which, in my opinion, is ridiculous. However, you can do a little p / invoke to get it:
public int Dpi { get { if (this.dpi == 0) { var desktopHwnd = new HandleRef(null, IntPtr.Zero); var desktopDC = new HandleRef(null, SafeNativeMethods.GetDC(desktopHwnd)); this.dpi = SafeNativeMethods.GetDeviceCaps(desktopDC, 88 ); if (SafeNativeMethods.ReleaseDC(desktopHwnd, desktopDC) != 1 ) {
So now you can change the corresponding line of code:
RenderTargetBitmap bity = new RenderTargetBitmap(300, 300, this.Dpi, this.Dpi, PixelFormats.Default);
And it will work regardless of the device on which you work. You will always have a raster image whose size is 300 × 300, and the source will always fill it accurately.