WPF RenderTargetBitmap black bars on right side and bottom - wpf

I want to save an image from my WPF user control. It works but I got black bars on the right side and the bottom. If I change the dpiX (96) and dpiY (96) it works but when I maximize the window, it's wrong again (then a bit of the usercontrol is missing ).
This is how I save the image as bitmap:
Dim parentWindow As Window = Window.GetWindow(_Map)
Dim rtb As New RenderTargetBitmap(parentWindow.ActualWidth, parentWindow.ActualHeight, 96, 96, PixelFormats.Pbgra32)
rtb.Render(_Map)
Dim ms As New MemoryStream()
Dim bp As New BmpBitmapEncoder()
bp.Frames.Add(BitmapFrame.Create(rtb))
bp.Save(ms)
Dim saveMap As New Bitmap(ms)

The width and height of the window are a bit larger than your UserControl because it includes the titlebar and borders. if you use the dimensions of your control instead it works fine:
Dim rtb As New RenderTargetBitmap(_Map.ActualWidth, _Map.ActualHeight, 96, 96, PixelFormats.Pbgra32)
I recommend you also make sure the control is layouted properly by calling Measure and Arrange with the current or desired size before rendering.
Additional size calculations may be neccessary if you are using the LayoutTransform or RenderTransform properties of your control.
Btw: WPF always uses 96 DPI for size calculations so you shouldn't change that.

Related

Direct2D With GDI+ Render Target Blending Transparent as Black

Reproduce the problem in this project:
https://github.com/adrotter/GDIDirect2DDualRenderer
I am trying to make Direct2D work with our current rendering engine, which is GDI+. I did this by abstracting all the calls to GDI+, and making a concrete class for GDI+ and for Direct2D. One issue that a lot of people seem to have is when you call Clear on the render target with a transparent color, you get completely opaque black background.
How do I clear a Direct2D render target to fully transparent
How to create a transparent bitmap in Direct2D
The answers from the above stack overflow posts suggest I need a WS_EX_LAYERED window, or using DirectComposition, but I am not sure if doing that would work well in WinForms. Besides, they are only talking about making the window transparent and not pixels on a bitmap. The solution I came up with that partially works is if there is a call to Graphics.Clear, and I have yet to start drawing using the render target, then I call Clear on the Drawing.Graphics object, and then start drawing. This will make the background completely transparent if I use Graphics.Clear(Color.Transparent) (fixes the problem people had with the black background).
Here is how that looks like with 100% opacity:
The color ramp on the left is Direct2D rendering, and the color ramp on the right is GDI+. They look similar, but the Direct2D looks darker. It gets worse.
Here's 50% opacity
1% opacity
Now this is why my solution partially works: 0% opacity works great. See here.
Now I don't actually think that the target blending black with transparent is a result of me calling Clear first with the System.Drawing.Graphics object first, because I tested the partial transparency without using my trick (I made no calls to GDI+ graphics at all). I would still get the same results as above for opacities 50 and 1. It seems like calling DrawBitmap using a bitmap with partial transparencies will always blend the partial transparent color with black.
For reference here is how my render target is being made:
Dim targetProperties As New RenderTargetProperties
targetProperties.Type = RenderTargetType.Default
targetProperties.PixelFormat = New Direct2D1.PixelFormat(Format.B8G8R8A8_UNorm, SharpDX.Direct2D1.AlphaMode.Premultiplied)
targetProperties.Usage = RenderTargetUsage.GdiCompatible
Dim factory = New Direct2D1.Factory()
_target = New DeviceContextRenderTarget(factory, targetProperties)
_target.BindDeviceContext(_graphics.GetHdc(), rec)
Also for reference here is how I turn a System.Drawing.Bitmap into a Direct2D1Bitmap:
Private Function GetDirect2DBitmap(bmp As System.Drawing.Bitmap) As SharpDX.Direct2D1.Bitmap
Dim bmpData As System.Drawing.Imaging.BitmapData = bmp.LockBits(
New System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height),
System.Drawing.Imaging.ImageLockMode.ReadOnly,
System.Drawing.Imaging.PixelFormat.Format32bppPArgb)
Dim stream As New SharpDX.DataStream(bmpData.Scan0, bmpData.Stride * bmpData.Height, True, False)
Dim pFormat As New SharpDX.Direct2D1.PixelFormat(SharpDX.DXGI.Format.B8G8R8A8_UNorm, Direct2D1.AlphaMode.Premultiplied)
Dim bmpProps As New SharpDX.Direct2D1.BitmapProperties(pFormat)
Dim result As New SharpDX.Direct2D1.Bitmap(
_targets(_selectedTargetIdx),
New SharpDX.Size2(bmp.Width, bmp.Height),
stream,
bmpData.Stride,
bmpProps)
bmp.UnlockBits(bmpData)
stream.Dispose()
Return result
End Function
And here is a simplification of getting my semi transparent bitmaps:
//using C# style comments so this looks prettier
//Make empty bitmap to draw on
dim canvasBMP = new System.Drawing.Bitmap(100,100)
//Make the bitmap that has partial transparent pixels
dim partialTransparentBMP as System.Drawing.Bitmap = GetSemiTransparentBMP(100,100)
Using g as System.Drawing.Graphics = Graphics.FromImage(canvasBMP)
g.Clear(Color.White)
dim d2dTarget = MakeRenderTarget(g, g.VisibleClipBounds)
d2dTarget.BeginDraw()
d2dTarget.DrawBitmap(GetDirect2DBitmap(partialTransparentBMP), GetDirect2DRect(...), 1.0F, BitmapInterpolationMode.Linear)
d2dTarget.EndDraw()
End Using
How do I get a white blended partial transparent pixel and not a black one using a GDI+ render target?

How to fix VisualBrush lost line?

In WPF ,we can use VisualBrush do some thing like ppt's left side.
But I see the VisualBrush may lost the line in Rectangle when I zoom the VisualBrush to a small size.Like the image:
You can see VisualBrush lost the Bottom line.
But what I want is like the below image:
When I try use the BitmapImage that use RenderTargetBitmap to get a image and use linear interpolation algorithm to zoom will get a clearness image.
Can I change VisualBrush's algorithm I think it may use neighborhood-pixels algorithm.
Are there any printscreen algorithm that have a good performance like VisualBrush.
When I change my search key to ViewBox ,I can find the same question as this one :how to avoid a single pixel line disappear in wpf?
There is a class named TransformedBitmap which can scale your RenderTargetBitmap with default scaling algorithm.
Use the code below:
public static BitmapSource ToBitmapSource(this Visual visual, Size size)
{
var bounds = VisualTreeHelper.GetDescendantBounds(visual);
var width = (int) Math.Round(bounds.Width);
var height = (int) Math.Round(bounds.Height);
var bitmap = new RenderTargetBitmap(width, height, 96.0, 96.0, PixelFormats.Pbgra32);
bitmap.Render(visual);
return new TransformedBitmap(bitmap, new ScaleTransform(size.Width / width, size.Height / height));
}
I've tried this method in my demo and got the result below. You may noticed that the small rectangle in the left-top corner lost nothing.

How to interchange D3DImage between a WPF application and Windows (for Copy/Paste)?

I'm being working in making copy and paste operations on my diagramming application.
Therefore, I need to...
Convert a WPF Bitmap to a D3DImage (for later implement "Copy"), and
Convert a D3DImage to a WPF Bitmap (for later implement "Paste").
Notice that this requieres "Interop" between the WPF Application and the native Win-32.
How to do this, as short as possible (two functions, ideally)?
You probably found your answer in the past half year but here is how to convert a D3DImage in WPF to BitmapSource (I used vb.net in this case):
Public Function GetCurrentImage() As BitmapSource
Dim width As Integer = D3DImage.PixelWidth
Dim height As Integer = D3DImage.PixelHeight
Dim dv As New DrawingVisual()
Using dc As DrawingContext = dv.RenderOpen()
dc.DrawImage(D3DImage, New Rect(0, 0, width, height))
End Using
Dim rtb As New RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32)
rtb.Render(dv)
Return BitmapFrame.Create(rtb)
End Function
You can find it in C# in the WPFMediaKit.
What do you mean by D3DImage?
There is a 'Control'/Surface in WPF that is named D3DImage but it feels as if you are referring to a bitmap format. Are you trying to put the data on the clipboard? If so you won't need interop. Just use the Clipboard API to put the image on the clipboard and read it from the clipboard.

Render a "not visible" WPF controls to an bitmap image

Render a WPF control to a bitmap image is not a trivial task as I found out today. As I know now dealing with the parent control margin is a problem, as Rick Strahl wrote in his blog
http://www.west-wind.com/weblog/posts/2007/Sep/10/Rendering-a-WPF-Container-to-Bitmap
So far I am able to create bitmaps of any control that is visible inside a window but what I really need to do is create bitmaps of invisible controls. I just create them in code - simples shapes like rectangle and ellipse - and want to save them as bitmaps to disk.
For me this turn out to be a personal nightmare. Since my ActualHeight and ActualWidth is always 0 I use Height and Width instead. But all I get is a empty image in the size of my control.
How can I create a bitmap of any control without adding it to a visible window?
New elements haven't had their layout performed. You need to call Measure and Arrange on the control before you render it.
Canvas c = new Canvas();
Rectangle r = new Rectangle
{
Fill = Brushes.Orange,
Width = 200,
Height = 100
};
Ellipse e = new Ellipse
{
Fill = Brushes.DodgerBlue,
Width = 100,
Height = 100
};
Canvas.SetLeft(e, 150);
c.Children.Add(r);
c.Children.Add(e);
var size = new Size(250,100);
c.Measure(size);
c.Arrange(new Rect(size));
RenderTargetBitmap bmp = new RenderTargetBitmap(250, 100, 96, 96, PixelFormats.Pbgra32);
bmp.Render(c);
PngBitmapEncoder enc = new PngBitmapEncoder();
enc.Frames.Add(BitmapFrame.Create(bmp));
using(var s = File.OpenWrite("image.png"))
enc.Save(s);

How to render a bitmap from a WPF element that has a bitmap effect?

I'm able to render a Visual to a bitmap fine with this code:
Rect bounds = VisualTreeHelper.GetDescendantBounds(target);
RenderTargetBitmap renderBitmap = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
DrawingVisual visual = new DrawingVisual();
using (DrawingContext context = visual.RenderOpen())
{
VisualBrush brush = new VisualBrush(target);bounds.Value.Size));
context.DrawRectangle(brush, null, new Rect(new Point(), bounds.Value.Size));
}
renderBitmap.Render(visual);
return renderBitmap;
The problem is that if the Visual has a bitmap effect like a drop shadow on it, then the resulting image is squished. It seems that its trying to fit the visual with the drop shadow into an image the size of the visual without the drop shadow.
In most cases (like drop shadow) the actual rendering of the effect falls outside of the bounds of the element itself. Relying on the ActualHeight and ActualWidth to size you image then causes the squeezing effect you're seeing. The best solution would be to use a parent container instead but that might require changes to your layout. You may also be able to calculate additional padding values to add to the element's size that will compensate for the effect rendering. It might be possible to derive those values by inspecting the properties of the Effect itself and will probably involve some trial and error too.
The effect has a set of padding properties and thes are used to set the size of rendering area used by the effect - see if these have been modified and if so adjust the size of the rendered visual. Have a look at RenderTargetBitmap - Visual vector to bitmap and the articles at WPF Workings

Resources