passing a vector drawing to the UI thread - wpf

Is there any way to draw actual WPF vectorgraphics (DrawingContext, VisualBrush, DrawingBrush, RenderTargetBitmap etc.) with Freezables in a separate thread offscreen?
The following solution almost has it, excpet that the drawing is a bitmap and is not scalable when this.label becomes big you'll the the pixels.
private void Draw()
{
this.dispatcher = Dispatcher.CurrentDispatcher;
Thread t = new Thread(this.DrawAsync);
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
private void DrawAsync(object state)
{
var b1 = new Button
{
Width = 50,
Height = 50,
Content = new TextBlock
{
FontSize = 16, FontFamily = new FontFamily("Arial"), FontWeight = FontWeights.Bold, Text = "Hello"
}
};
b1.Measure(new Size(50, 50));
b1.Arrange(new Rect(0, 0, 50, 50));
PixelFormat pixelFormat = PixelFormats.Default;
var elementBrush = new VisualBrush(b1);
var visual = new DrawingVisual();
using (var dc = visual.RenderOpen())
{
// preferably I'd like to draw controls too, but shapes and text would suffice too
dc.DrawRectangle(elementBrush, null, new Rect(0, 0, 50, 50));
dc.DrawEllipse(Brushes.Green, new Pen(Brushes.Black, 2), new Point(75, 25), 25, 15);
dc.Close();
}
var bitmap = new RenderTargetBitmap(100, 50, 96, 96, pixelFormat);
bitmap.Render(visual);
bitmap.Freeze();
var br = new ImageBrush(bitmap) { Stretch = Stretch.Uniform };
br.Freeze();
this.dispatcher.Invoke((ThreadStart)delegate { this.label.Background = br; });
}

You could use DrawingImage which is a type of freezeable. Load or fill it in a BackgroundWorker, freeze it and pass it to an Image in the Completed Event.

Related

How to have a second color for a drawing group in wpf

I need to be able to draw horizontal lines against a background color, say red. I have the lines being drawn, but I don't know how to set the background color. Right now, the background is white.
GeometryGroup stdGeometryGroup = new GeometryGroup();
DrawingBrush db = new DrawingBrush();
GeometryDrawing stdDrawing = new GeometryDrawing(null, pen, stdGeometryGroup);
if (stdDrawing != null)
{
db.Drawing = stdDrawing;
db.ViewboxUnits = BrushMappingMode.Absolute;
db.ViewportUnits = BrushMappingMode.Absolute;
db.Viewbox = new Rect(0, 0, 30, 30);
db.Viewport = new Rect(0, 0, 4, 4);
db.TileMode = TileMode.Tile;
db.Stretch = Stretch.UniformToFill;
db.Transform = new RotateTransform(45, 0.5, 0.5);
stdGeometryGroup.Children.Add(new LineGeometry(new Point(0, 15), new Point(30, 15)));
menuItem.Background = db;
}
In this case, menuItem is a Telerik RadMenuItem. The Pen is black.

WPF Rotate and return BitmapSource by any angle

Hei, I tried this:
public static BitmapSource RotateImage(Image b, float angle)
{
BitmapSource rotita = (BitmapSource)b.Source;
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
var transform = new RotateTransform(angle);
drawingContext.PushTransform(transform);
drawingContext.DrawImage(rotita, new Rect(0,0, rotita.PixelWidth, rotita.PixelHeight));
drawingContext.Pop();
}
RenderTargetBitmap bmp = new RenderTargetBitmap(rotita.PixelWidth, rotita.PixelHeight, 96, 96, PixelFormats.Pbgra32);
bmp.Render(drawingVisual);
rotita = bmp;
return rotita;
}
But this does not work fine. I have this image at 0 degree and after rotation at 30 degrees this image.
What could I make the picture to be complete after rotation?
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
drawingContext.DrawImage(back, new Rect(0, 0, imageWidth, imageHeight));
drawingContext.DrawImage(element, new Rect(x,y, elementWidth, elementHeight));
}
RenderTargetBitmap bmp = new RenderTargetBitmap(imageWidth, imageHeight, 96, 96, PixelFormats.Pbgra32);
bmp.Render(drawingVisual);
image.Source = bmp;
Element is the rotated image
The following method creates a composed bitmap from two others, where the second one is rotated around their common center point.
The two crucial parts of this method are the calculation of the transformed bounds of the rotated bitmap, and the alignment of the two bitmaps at their common center point.
private BitmapSource ComposeImage(
BitmapSource image1, BitmapSource image2, double rotationAngle)
{
var rotation = new RotateTransform(rotationAngle);
var size1 = new Size(image1.PixelWidth, image1.PixelHeight);
var size2 = new Size(image2.PixelWidth, image2.PixelHeight);
var center1 = new Vector(size1.Width / 2, size1.Height / 2);
var center2 = new Vector(size2.Width / 2, size2.Height / 2);
var rotatedSize = rotation.TransformBounds(new Rect(size2)).Size;
var totalSize = new Size(
Math.Max(size1.Width, rotatedSize.Width),
Math.Max(size1.Height, rotatedSize.Height));
var center = new Point(totalSize.Width / 2, totalSize.Height / 2);
rotation.CenterX = center.X;
rotation.CenterY = center.Y;
var dv = new DrawingVisual();
using (var dc = dv.RenderOpen())
{
dc.DrawImage(image1, new Rect(center - center1, size1));
dc.PushTransform(rotation);
dc.DrawImage(image2, new Rect(center - center2, size2));
}
var rtb = new RenderTargetBitmap(
(int)totalSize.Width, (int)totalSize.Height, 96, 96, PixelFormats.Default);
rtb.Render(dv);
return rtb;
}

shadow on geometry drawn by drawcontext in wpf

I am rendering a rectangle on a textbox using the drawing context using the following code.
drawingContext.DrawRoundedRectangle(
new SolidColorBrush(Color.FromRgb(255, 246, 178)), null,
new Rect(new Point(rect.TopRight.X + 20, rect.TopRight.Y),
new Size(130, rect.Height)),
3,
3);
I want to render a shadow on this rectangle that I draw programmatically in WPF. How can i do it ?
Add effect to Visual
Try something like this
public class MyControl: Control
{
private Rect rect = new Rect(100, 100, 200, 200);
protected override void OnRender(DrawingContext drawingContext)
{
var r = new Rect(new Point(rect.TopRight.X + 20, rect.TopRight.Y),
new Size(130, rect.Height));
var brush = new SolidColorBrush(Color.FromRgb(255, 246, 178));
DropShadowEffect effect = new DropShadowEffect();
effect = new DropShadowEffect {Color = Colors.Gainsboro, Direction = 30};
this.Effect = effect;
drawingContext.DrawRoundedRectangle(brush, null, r, 3, 3);
base.OnRender(drawingContext);
}
}
This gives me:
EDIT
If you do not have UI element to attach Effect, then you need to do shadow on your own.
Just add another rectangle under your main, with some gradient brush that becomes transparent.
protected override void OnRender(DrawingContext drawingContext)
{
var r = new Rect(new Point(rect.TopRight.X + 20, rect.TopRight.Y),
new Size(130, rect.Height));
var r2 = new Rect(new Point(rect.TopRight.X + 25, rect.TopRight.Y+5),
new Size(130, rect.Height));
var brush = new SolidColorBrush(Color.FromRgb(255, 246, 178));
var gradientBrush = new LinearGradientBrush(Colors.Black, Colors.Gray, 30);
drawingContext.DrawRoundedRectangle(gradientBrush, null, r2, 3, 3);
drawingContext.DrawRoundedRectangle(brush, null, r, 3, 3);
base.OnRender(drawingContext);
}
This will give you something like this

Snapshot of an WPF Canvas Area using RenderTargetBitmap

I want to create a Snapshot of the Canvas Area in my Application. I'm using Visual brush to get the Snapshot and saving the same using PngEncoder. But the resulting PNG is just a empty black image. I'm not sure the issue is with the BitmapSource created or the PNGEncoder issue. Here is the code I'm using to obtain the same.
public void ConvertToBitmapSource(UIElement element)
{
var target = new RenderTargetBitmap((int)(element.RenderSize.Width), (int)(element.RenderSize.Height), 96, 96, PixelFormats.Pbgra32);
var brush = new VisualBrush(element);
var visual = new DrawingVisual();
var drawingContext = visual.RenderOpen();
drawingContext.DrawRectangle(brush, null, new Rect(new Point(0, 0),
new Point(element.RenderSize.Width, element.RenderSize.Height)));
drawingContext.Close();
target.Render(visual);
PngBitmapEncoder encoder = new PngBitmapEncoder();
BitmapFrame outputFrame = BitmapFrame.Create(target);
encoder.Frames.Add(outputFrame);
using (FileStream file = File.OpenWrite("TestImage.png"))
{
encoder.Save(file);
}
}
Not sure why exactly your code isn't working. This works:
public void WriteToPng(UIElement element, string filename)
{
var rect = new Rect(element.RenderSize);
var visual = new DrawingVisual();
using (var dc = visual.RenderOpen())
{
dc.DrawRectangle(new VisualBrush(element), null, rect);
}
var bitmap = new RenderTargetBitmap(
(int)rect.Width, (int)rect.Height, 96, 96, PixelFormats.Default);
bitmap.Render(visual);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
using (var file = File.OpenWrite(filename))
{
encoder.Save(file);
}
}
Thank you both for the question and the answer.
For the benefit of the others looking for the same answer
I found that Clemens way leaves a black band in the image with the image shifted either down or right. As if it was not rendering the element at the correct position in the bitmap.
So I had to use the VisualBrush as Amar suggested.
Here is the code that worked for me:
RenderTargetBitmap RenderVisual(UIElement elt)
{
PresentationSource source = PresentationSource.FromVisual(elt);
RenderTargetBitmap rtb = new RenderTargetBitmap((int)elt.RenderSize.Width,
(int)elt.RenderSize.Height, 96, 96, PixelFormats.Default);
VisualBrush sourceBrush = new VisualBrush(elt);
DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
using (drawingContext)
{
drawingContext.DrawRectangle(sourceBrush, null, new Rect(new Point(0, 0),
new Point(elt.RenderSize.Width, elt.RenderSize.Height)));
}
rtb.Render(drawingVisual);
return rtb;
}

Convert WPF Control to BitmapSource

This is kind of a two part question- First, why doesn't this code work?
Canvas canvas = new Canvas { Width = 640, Height = 480 };
System.Windows.Size size = new System.Windows.Size( canvas.Width, canvas.Height);
//Measure and arrange the surface
canvas.Measure( size );
canvas.Arrange( new Rect( size ) );
canvas.Background = new SolidColorBrush( Colors.Purple );
RenderTargetBitmap bitmap = new RenderTargetBitmap( (int)canvas.Width, (int)canvas.Height, 96d, 96d, PixelFormats.Pbgra32 );
bitmap.Render( canvas );
BitmapEncoder encoder = new BmpBitmapEncoder();
encoder.Frames.Add( BitmapFrame.Create( bitmap ) );
using ( MemoryStream outStream = new MemoryStream() )
{
encoder.Save( outStream );
outStream.Seek( 0, SeekOrigin.Begin );
BitmapImage bmp = new BitmapImage { CacheOption = BitmapCacheOption.OnLoad };
bmp.BeginInit();
bmp.StreamSource = outStream;
bmp.EndInit();
}
When I write the image to disk, all I see is a black image- I've done this before and had no problems, but now something is escaping me... I've checked the width and height and the buffer data in the MemoryStream and everything look okay...
This is just a test, the real goal would be to create a BitmapSource from the Canvas visual image. The Canvas is getting drawn on with Shapes (polylines etc) in code. I then need to pass this BitmapSource to an Image in xaml, at a rate of about 60 frames per second. I noticed that the Image.Source is using CachedBitmap if I create a mock BitmapSource, but it is rebinding to my BitmapImage everytime I update my (black) Bitmap.
Suggestions on how to create a Canvas in memory at 60fps and create a BitmapSource from it that is seen by Image.Source as a CachedBitmap?
Makubex is correct - you need to wait until things get loaded up before the visuals are in a state where they've actually rendered anything capable of being copied; that said, while I'm not on a computer where I've got Studio installed, I do have LINQPad installed....
void Main()
{
mainWindow = new Window(){ Width = 640, Height = 480, Title = "Main Window" };
canvas = new Canvas { Width = 640, Height = 480 };
System.Windows.Size size = new System.Windows.Size( canvas.Width, canvas.Height );
// Measure and arrange the surface
canvas.Measure( size );
canvas.Arrange( new Rect( size ) );
canvas.Background = new SolidColorBrush( Colors.Purple );
mirrorTimer = new DispatcherTimer(
TimeSpan.FromMilliseconds(1000.0 / 60.0),
DispatcherPriority.Background,
CopyToMirror,
Dispatcher.CurrentDispatcher);
updateTimer = new DispatcherTimer(
TimeSpan.FromMilliseconds(1000.0 / 60.0),
DispatcherPriority.Background,
DrawSomething,
Dispatcher.CurrentDispatcher);
mainWindow.Loaded +=
(o,e) =>
{
mirrorWindow = new Window { Width = 640, Height = 480, Title = "Mirror Window" };
mirrorWindow.Show();
mirrorWindow.Loaded +=
(o2,e2) =>
{
mirrorTimer.Start();
};
};
mainWindow.Closed +=
(o,e) =>
{
if(mirrorTimer != null)
{
mirrorTimer.Stop();
mirrorWindow.Close();
}
};
mainWindow.Content = canvas;
mainWindow.Show();
}
Window mainWindow;
Window mirrorWindow;
Canvas canvas;
DispatcherTimer mirrorTimer;
DispatcherTimer updateTimer;
Random rnd = new Random();
private void DrawSomething(object sender, EventArgs args)
{
canvas.Children.Clear();
canvas.Background = Brushes.White;
var blob = new Ellipse() { Width = rnd.Next(0, 20), Height = rnd.Next(0, 20) };
blob.Fill = new SolidColorBrush(Color.FromArgb(255, (byte)rnd.Next(0,255), (byte)rnd.Next(0,255), (byte)rnd.Next(0,255)));
Canvas.SetLeft(blob, (int)rnd.Next(0, (int)canvas.ActualWidth));
Canvas.SetTop(blob, (int)rnd.Next(0, (int)canvas.ActualHeight));
canvas.Children.Add(blob);
}
private void CopyToMirror(object sender, EventArgs args)
{
var currentImage = (mirrorWindow.Content as Image);
if(currentImage == null)
{
currentImage = new Image(){ Width = 640, Height = 480 };
mirrorWindow.Content = currentImage;
}
RenderTargetBitmap bitmap = new RenderTargetBitmap( (int)canvas.Width, (int)canvas.Height, 96d, 96d, PixelFormats.Pbgra32 );
bitmap.Render( canvas );
BitmapEncoder encoder = new BmpBitmapEncoder();
encoder.Frames.Add( BitmapFrame.Create( bitmap ) );
BitmapImage bmp = new BitmapImage() { CacheOption = BitmapCacheOption.OnLoad };
MemoryStream outStream = new MemoryStream();
encoder.Save(outStream);
outStream.Seek(0, SeekOrigin.Begin);
bmp.BeginInit();
bmp.StreamSource = outStream;
bmp.EndInit();
currentImage.Source = bmp;
}
If you are still having issues with the black image move your CacheOption set to just after the BeingInit call:
bmp.BeginInit();
bmp.CacheOption = BitmapCacheOption.OnLoad;
bmp.StreamSource = outStream;
bmp.EndInit();

Resources