How to fix VisualBrush lost line? - wpf

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.

Related

Drawing to a bitmap from a WPF canvas

I have a canvas that contains an Image in which I dislay an existing BMP. I draw rectangles on the canvas and add these to the Children colllection. When I click save, I want to update the underlying BMP file.
The following code works, but the rectangle that gets drawn to the BMP is way smaller than what I drew. I guess there's some difference in the co-ordinates? Maybe I shouldn't be using System.Drawing?
using (Graphics g = Graphics.FromImage(image))
{
g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
foreach (var child in canvas.Children)
{
if (child is System.Windows.Shapes.Rectangle)
{
var oldRect = child as System.Windows.Shapes.Rectangle;
// need to do something here to make the new rect bigger as the scale is clearly different
var rect = new Rectangle((int)Canvas.GetLeft(oldRect), (int)Canvas.GetTop(oldRect), (int)oldRect.Width, (int)oldRect.Height);
g.FillRectangle(Brushes.Black, rect);
}
}
... code to save bmp
All suggestions welcome!
Thanks
Try using the System.Windows.Media.Imaging.RenderTargetBitmap Class (an example here).
Wpf uses Device Independent Graphics so you have to compensate for the DPI :
RenderTargetBitmap bmp = new RenderTargetBitmap((int)Canvas1.Width, (int)Canvas1.Height, 96, 96, PixelFormats.Default);
bmp.Render(Canvas1);
From Third Link:
There are two system factors that determine the size of text and graphics on your screen: resolution and DPI. Resolution describes the number of pixels that appear on the screen. As the resolution gets higher, pixels get smaller, causing graphics and text to appear smaller. A graphic displayed on a monitor set to 1024 x 768 will appear much smaller when the resolution is changed to 1600 x 1200.

Saving multiple canvas controls as an image in silverlight/wpf

I have a situation where I have a canvas with an image and I lay multiple canvas controls over it and each canvas may have multiple controls drawn on them (Ellipses, Lines, etc). What is the best/most efficient way to save all these canvas controls toone image? I assume WritableBitmap, but how will this work with multiple canvas controls? One given is that each canvas will be the same size.
EDIT:
This is what I tried. I also tried with the dpi of the original image, which gives me the tiny result.
BitmapSource bms = (BitmapSource)this.ctlImage.Source;
Matrix m = PresentationSource.FromVisual(this.ctlImgCanvas).CompositionTarget.TransformToDevice;
RenderTargetBitmap rtb = new RenderTargetBitmap(bms.PixelWidth, bms.PixelHeight, m.M11 * 96.0, m.M22 * 96.0, PixelFormats.Pbgra32);
rtb.Render(this.ctlImgCanvas);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(rtb));
encoder.Save(memStream);
This will take care of everything including children of the canvas. You don't need to know the size of the canvas in advance.
Matrix m =
PresentationSource.FromVisual(canvas).CompositionTarget.TransformToDevice;
BitmapSource panelBitmap = new RenderTargetBitmap(
(int)Math.Floor(canvas.ActualWidth),
(int)Math.Floor(canvas.ActualHeight),
m.M11 * 96.0, // For DPI. This hardcoded value is correct. Check WPF DPI
m.M22 * 96.0,
PixelFormats.Default);
panelBitmap.Render(canvas);
using (FileStream fs = File.Open([filename], FileModel.Create, FileAccess.Write))
{
BitmapEncoder encoder = GetBitmapEncoderForFileExtension([filename]); // See documentation for which encoder to use when.
encoder.Frames.Add(BitmapFrame.Create(panelBitmap ));
encoder.Save(fs);
}
Save the container of the Canvas's to a bitmap, it'll pick up all the children.

WriteableBitmap adding black padding to right

I'm trying to make an Image out of one of my Canvas (myPrintingCanvas). But the Width of the image is getting atleast twice wider than the Canvas, and the extra space that is created is back. If I try on another Canavas (LayoutRoot), it works as intended.
My observation is that on myPrintingCanvas the ActualWidth is always 0. LayoutRoot has a correct ActualWidth. Not sure if it has anything to do with the extra padding, and I have failed on getting the ActualWidth for myPrintingCanvas (using UpdateLayout and Measure).
Code:
//Code to render the content of myPrintingCanvas
...
//Make the WriteableBitmap
WriteableBitmap myWriteableBitmap = new WriteableBitmap(myPrintingCanvas, null);
Have you tried Creating the WriteableBitmap with a fixed size and then calling its Render method to render the bitmap? Canvas has some weird behaviors because it is an anti-pattern in Silverlight and WPF and breaks many rules about layout. The code for creating a fixed size WriteableBitmap and rendering to it would look something like this:
int width = 640;
int height = 480;
WriteableBitmap bmp = new WriteableBitmap(width, height);
bmp.Render(myPrintingCanvas, null);
bmp.Invalidate();
One advantage of this technique is that if you need to grab multiple images you can reuse the same WriteableBitmap instead of recreating it every time.

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

Ellipse geometry bound points

Is there an automatic way to get all the points of an ellipse stroke, without the filling points?
In WPF there are no actual "Points" in a geometry - it is infinitely smooth. This can be seen by zooming in on an ellipse. You can go to 1,000,000x zoom and you can still see curvature and no points.
Since WPF shapes aren't composed of points, your question can be interepted in several ways. You may be looking for any of these:
A list of points that approximates the boundary of the ellipse (polyline approximation)
A set of pixels covered by the ellipse including the fill
A set of pixels covered by the edge of the ellipse
Here are the solutions in each case:
If you're looking for an approximation of the ellipse as discrete points (ie. a dotted-line version that looks like an ellipse), use this code:
PolyLineSegment segment =
ellipse.DefiningGeometry
.GetFlattenedPathGeometry(1.0, ToleranceType.Absolute)
.Figures[0].Segments[0] as PolyLineSegment;
foreach(Point p in segment.Points)
...
If you're looking for the pixels affected, you'll need to RenderTargetBitmap:
RenderTargetBitmap rtb =
new RenderTargetBitmap(width, height, 96, 96, PixelFormat.Gray8);
rtb.Render(ellipse);
byte[] pixels = new byte[width*height];
rtb.CopyPixels(pixels, width, 0);
Any nonzero value in pixels[] is partially covered by the ellipse. This will include points interior to the ellipse if the ellipse has a fill.
If you need to get only the pixels along the edge but your ellipse is filled, or vice versa, you can create a new Shape to pass to RenderTargetBitmap:
var newEllipse = new Path
{
Data = ellipse.DefiningGeometry,
Stroke = Brushes.Black,
};
RenderTargetBitmap rtb = ...
[same as before]
By using Reflector I found out that there is a GetPointList() method in the EllipseGeometry class unfortunately it's private. Maybe you can invoke it through reflection but that's sounds like a very bad hack... I'll see if I find another way...

Resources