My requirements:
a persistent UserControl that handles logic for a custom image, such as a map or drawing
a set of containers to implement caching on the image during zoom or pan movements
VisualBrush copies of the UserControl that I can add to the containers for use with Effects
I currently implement image caching with a RenderTargetBitmap, but that seems to have trouble with the VisualBrush-covered Rectangle objects I'm using.
My question: What can I add/change in this code to get the VisualBrush objects to render correctly after RenderTargetBitmap uses them? What strange thing is RenderTargetBitmap doing that makes the VisualBrush invisible?
This is a problem that I have been unable to reproduce without a decent amount of code.
In my xaml file I have:
<Window x:Class="ElementRender.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="350" Width="525">
<Grid>
<Grid Name="_contentContainer">
<Rectangle Fill="White"/>
<Grid Name="_content">
<Grid Name="_back"/>
<Grid Name="_body"/>
</Grid>
</Grid>
<StackPanel VerticalAlignment="Bottom" Orientation="Horizontal">
<Button Content="New" Name="New"/>
<Button Content="Move" Name="Move"/>
<Button Content="Update" Name="Update"/>
</StackPanel>
</Grid>
</Window>
and the .xaml.cs:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
public partial class MainWindow : Window
{
private const int imageWidth = 150;
private const int imageHeight = 150;
private readonly UserControl Control;
public MainWindow()
{
InitializeComponent();
// User Control setup
Control = new UserControl() {
Width = imageWidth, Height = imageHeight,
Content = BuildImage()
};
_body.Children.Add(SoftCopy(Control));
// event setup
Move.Click += (sender, e) => _content.RenderTransform = new TranslateTransform(50, 50);
New.Click += (sender, e) => {
HardCopy();
_content.RenderTransform = null;
Control.Content = BuildImage();
};
}
private FrameworkElement BuildImage()
{
return new Rectangle{Fill=Brushes.Blue};
}
private void HardCopy()
{
int width = (int) _contentContainer.ActualWidth;
int height = (int) _contentContainer.ActualHeight;
// render the current image
var rtb = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
DrawingVisual dv = new DrawingVisual();
using (var context = dv.RenderOpen())
{
var brush = new VisualBrush(_contentContainer) { Opacity = .5 };
context.DrawRectangle(brush, null, new Rect(0, 0, width, height));
}
rtb.Render(dv);
var lastRender = new Image
{
Source = rtb,
Stretch = Stretch.None,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Width = width,
Height = height
};
_back.Children.Clear();
_back.Children.Add(lastRender);
}
private FrameworkElement SoftCopy(FrameworkElement element)
{
return new Rectangle{Fill= new VisualBrush(element), Width=element.Width, Height=element.Height};
}
}
A few helping notes about the code:
the xaml's _contentContainer works with HardCopy() to copy the current images into the image cache, _back.
SoftCopy returns a FrameworkElement that looks exactly like the one past in, but without any transforms, effects, or visual parents. This is very important.
BuildImage simulates building a new image to be pasted over the cache after the initial image has been transformed somehow.
If you build and run the application removing the SoftCopy() from the _body.Children.Add(SoftCopy(Control));, you see the effect that I want to get: the new element is pasted above the old element, and the old element seems to retain its transform.
Alternatively, if you cut out the line var rtb = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32); from HardCopy, the caching function is broken, but the SoftCopy is displayed correctly.
However, if you run the application as-is, you notice that the new BlueRectangle (as rendered through a VisualBrush) doesn't display at all, until you hit the "New" button again, pushing the image to the cache, and still not showing you the new created image.
I'm going to be pompous enough to call this a bug in WPF. I eventually found out how to fix the strange behavior I was getting:
var visual = visualBrush.Visual;
visualBrush.Visual = null;
visualBrush.Visual = visual;
This should essentially be a null operation: by the end, the visual brush has the same visual as when it started. However, adding this code segment after rendering the VisualBrush into the RenderTargetBitmap fixed the issue I was having.
I didn't quite understand the post but there are few important things:
If you apply RenderTransform/Margins to element and take picture of it(RenderTargetBItmap), you're gonna have bad time. It will be offseted and you will get only sub-picture.
The idea is to take picture without any rendertransforms, and then later copy RenderTransform over from the old one. If needed.
Related
In a page I display a picture that I receive from CameraCapureTask in the View
<Grid x:Name="EditPageGrid" Margin="{Binding}">
<Grid Name="ViewportContainer" Margin="12,0,12,24">
<Image x:Name="Viewport" LayoutUpdated="Viewport_LayoutUpdated"
Source="{Binding}"/>
</Grid>
</Grid>
And I wish to be able to place a border around this image. How would it be possible to do this? I was thinking perhaps on a click event of some sort a border could be toggled on or off, but actually applying a border is where I am at a loss.
You can contain the image in a Border, like this:
<Grid x:Name="EditPageGrid" Margin="{Binding}">
<Grid Name="ViewportContainer" Margin="12,0,12,24">
<Border HorizontalAlignment="Center" BorderThickness="4" BorderBrush="Red">
<Image Source="C:\Users\Public\Pictures\Sample Pictures\Jellyfish.jpg"/>
</Border>
</Grid>
</Grid>
I figured out such an event: (probably there is better method, but this also works)
private void Viewport_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
int imageHeight = (Viewport.Source as BitmapImage).PixelHeight;
int imageWidth = (Viewport.Source as BitmapImage).PixelWidth;
Canvas myCanvas = new Canvas();
Rectangle myBorder = new Rectangle();
myBorder.Width = imageWidth;
myBorder.Height = imageHeight;
myBorder.Stroke = new SolidColorBrush(Colors.Red);
myBorder.StrokeThickness = 10;
Image toBorder = new Image();
toBorder.Source = Viewport.Source as BitmapImage;
myCanvas.Children.Add(toBorder);
myCanvas.Children.Add(myBorder);
WriteableBitmap newImage = new WriteableBitmap(myCanvas, null);
//Viewport.Source = newImage; - you can use this but watch out that Viewport.Source now is not BitmapImage
//Below is one method how to make it BitmapImage
//You can of course save newImage to file or whatever you want
//You can also unsubscribe this event to prevent it from second tap which will cause Exception at first line (BitmaImage != WriteableBitmap)
MemoryStream memoryStream = new MemoryStream();
newImage.SaveJpeg(memoryStream, imageWidth, imageHeight, 0, 100);
BitmapImage newBitmap = new BitmapImage();
newBitmap.SetSource(memoryStream);
Viewport.Source = newBitmap;
}
Playing with this memory stream isn't good, but I've not known what you are planning to do with your new Bitmap.
As I've said - it's only example and I'm sure better methods exist (which I don't know). Hope this helps.
Below is the code for a simple app that draws a rectangle on a canvas in a window and then takes a screen shot of the app using the CopyFromScreen function when any key is pressed. Just before this is called however, I call canvas.Children.Clear(). I would then expect the resultant image to not have the rectangle in it, but it does. It seems that the actual rectangle image isn't removed from the canvas when the function is called but some time after.
I tried putting in a System.Threading.Thread.Sleep(1000); after the Clear() call but the rectangle stays on screen for that full second as well. Clearly it's getting removed after the key press function finishes, is there any way to remove it before the CopyFromScreen call?
To run this you will need to add a reference to System.Drawing.
XAML code
<Window x:Class="CanvasTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Width="210" Height="240"
KeyDown="keyPressed">
<Window.Background>
<SolidColorBrush Color="White"/>
</Window.Background>
<Grid>
<Canvas Name="canvas"
HorizontalAlignment="Left" VerticalAlignment="Top">
</Canvas>
</Grid>
</Window>
.cs code
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Shapes;
namespace CanvasTest {
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
Left = 0;
Top = 0;
Rectangle rect = new Rectangle {
Stroke = System.Windows.Media.Brushes.Black,
StrokeThickness = 1,
Width = 100,
Height = 100
};
canvas.Children.Add(rect);
Canvas.SetLeft(rect, 50);
Canvas.SetTop(rect, 50);
}
private void keyPressed(object sender, System.Windows.Input.KeyEventArgs e) {
System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap((int)Width, (int)Height);
System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(bitmap);
canvas.Children.Clear();
graphics.CopyFromScreen(0, 0, 0, 0,
new System.Drawing.Size(bitmap.Width, bitmap.Height),
System.Drawing.CopyPixelOperation.SourceCopy);
String path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
path += "\\MuckleEwesPic.png";
bitmap.Save(path, System.Drawing.Imaging.ImageFormat.Png);
}
}
}
How can I clear the canvas and then take a screen shot without this behaviour happening? And no 'don't add the rect' isn't a solution ha, this is just a minimal example of a larger app in which the problem is occurring.
Thanks.
Using something like in ColinSmith's link:
static void WaitForRenderPass()
{
Application.Current.Dispatcher
.BeginInvoke( DispatcherPriority.ApplicationIdle, new Action( () => {} ) )
.Wait();
}
sort of works for waiting on a render pass but the problem is it's not possible (afaik) to tell if that render pass contains everything you want.
In practice depending on system load/video card drivers/... it has to be called multiple times. I've had one machine where it had to be called in a loop up to 3 times, and each of those calls took about 2 frames #60Hz to complete. Only then a call to the above would return immediately indicating the render thread is really idle, which likely means all changes you want have been rendered, which in turn means screen captures contain everything they should have. So we ended up using
for( int i = 0 ; i < 5 ; ++i )
{
WaitForRenderPass();
Thread.Sleep( 10 );
}
It's ugly and it's a hack but it hasn't failed (yet).
Regarding HighCore's comment: you can capture the screen with Wpf-only classes as well, however:
it has the same problem the OP started this questiomn for in the first place so it won't immediately solve anything (except from not using System.Drawing)
you won't be able to get a main window's chrome in the capture
it's markedly slower than CopyScreen
it does not render the exact same pixels as rendered to the screen
I'm a rookie at C# and WPF and I'm trying to create a simple car-simulator. Mainly the idea of the simulator is that I have C#-class that creates car-objects that have for example speed variable that can be changed and timer for moving from left to right. I want to do movement with timer and not for example doubleanimation. In WPF I have AddCarButton for adding cars in certain points in Canvas.
The problem is I dont know how to add cars to Canvas. This is very frustrating because it doesn't sound like a big thing to do but I feel like I have tried everything and not succeeded.
This is latest attempt with car-class. I have tried using Canvas.Set-methods but failed.
class car
{
private int speed;
public car(int s)
{
speed = s;
Bitmap bmp = new Bitmap(
System.Reflection.Assembly.GetEntryAssembly().
GetManifestResourceStream("MyProject.Resources.car.png"));
Graphics g = Graphics.FromImage(bmp);
//Canvas.SetBottom(g, 0);
//Canvas.SetLeft(g, 0);
//Canvas.SetBottom(bmp, 0);
//Canvas.SetLeft(bmp, 0);
}
public void addCar(car c)
{
Canvas.SetBottom(c, 0);
Canvas.SetLeft(c, 0);
}
If you're coding on WPF you shouldn't use Windows Forms stuff. To work with images you use BitmapSource and its derived classes, and to access your resources programmatically you usually use pack URIs. It's not the only way, though.
Here is a little example that draws some images on a canvas control.
The XAML code for the canvas could be like this (it's just an example):
<Canvas Height="400" HorizontalAlignment="Left" Margin="0" Name="canvas1" VerticalAlignment="Top" Width="400" />
and your main window code...
public partial class MainWindow : Window
{
BitmapImage carBitmap = new BitmapImage(new Uri("pack://application:,,,/Images/BlueCar.png", UriKind.Absolute));
Image[] carImg = new Image[5];
Random rnd = new Random();
public MainWindow()
{
InitializeComponent();
double maxX = canvas1.Width - carBitmap.Width;
double maxY = canvas1.Height - carBitmap.Height;
for (int i = 0; i < carImg.Length; i++)
{
carImg[i] = new Image();
carImg[i].Source = carBitmap;
carImg[i].Width = carBitmap.Width;
carImg[i].Height = carBitmap.Height;
Canvas.SetLeft(carImg[i], rnd.NextDouble() * maxX);
Canvas.SetTop(carImg[i], rnd.NextDouble() * maxY);
canvas1.Children.Add(carImg[i]);
}
}
}
Obviously you need change the name of your image resource. By the way, to add an image go to Project > Add existing item... and select your image file, now your image will appear in the Solution explorer (by default, Visual Studio stores image resources in a folder called "Images"), if you select it you'll see in the Properties window that its Build action is Resource, don't change this! (some people think it should be Embedded resource but that's incorrect).
If you don't get this new Uri("pack://application:,,,/Images/BlueCar.png", UriKind.Absolute), you should read this link on pack URIs.
You need to put your bitmap in an Image (and not Graphics), and then you need to add the image to the canvas:
Canvas.Children.Add(image);
I have an image viewer created with WPF 3D graphics. Image quality is really WORSE there, so I've started researching this issue, created simple application which shows the image using 2D graphics on the top part of the window, and the same image on the bottom part using 3D graphics. I noticed that image looks much worse on 3D surface than on 2D. The colors on the 3D surface are less saturated and do not have clear boundaries. Note, that I applied linear bitmap scaling mode to the root Grid. Other weird thing is that when I'm changing bitmap scaling mode to 'Fant' or 'NearestNeighbor' it affects 2D graphics, but image on the 3D surface REMAINS THE SAME! I'm using image for this sample with Height = 466px, Width = 490px. I'm zooming out it in the code (both 2D and 3D implementation) a little bit to see the scaling quality degradation. The code is:
<Window x:Class="Scaling3DSample.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="340">
<Grid x:Name="backgroundGrid">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
</Grid>
</Window>
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Shapes;
namespace Scaling3DSample
{
public partial class Window2 : Window
{
private static double _distanceFromCamera = 0.62618;
public Window2()
{
InitializeComponent();
RenderOptions.SetBitmapScalingMode(backgroundGrid, BitmapScalingMode.Linear);
Create2DGraphics();
// THE SAME IMAGE ON 3D SURFACE LOOKS MUCH WORSE
Create3DGraphics();
}
private void Create2DGraphics()
{
Rectangle exampleRectangle = new Rectangle();
Grid.SetRow(exampleRectangle, 0);
exampleRectangle.Width = 335;
exampleRectangle.Height = 317;
exampleRectangle.Fill = GetBrush();
backgroundGrid.Children.Add(exampleRectangle);
}
private void Create3DGraphics()
{
Viewport3D mainViewPort3D = new Viewport3D();
Grid.SetRow(mainViewPort3D, 1);
mainViewPort3D.Camera = new PerspectiveCamera { LookDirection = new Vector3D(-1, 0, 0), UpDirection = new Vector3D(0, 0, 1), FieldOfView = 77.0942 };
mainViewPort3D.Children.Add(new ModelVisual3D { Content = new AmbientLight() });
MeshGeometry3D geometry3D = new MeshGeometry3D();
Point3D topLeft = new Point3D(-_distanceFromCamera, 0.5, -0.5);
Point3D bottomRight = new Point3D(-_distanceFromCamera, -0.5, 0.5);
geometry3D.Positions.Add(bottomRight);
geometry3D.Positions.Add(new Point3D(-_distanceFromCamera, topLeft.Y, bottomRight.Z));
geometry3D.Positions.Add(new Point3D(-_distanceFromCamera, bottomRight.Y, topLeft.Z));
geometry3D.Positions.Add(topLeft);
geometry3D.TriangleIndices.Add(1);
geometry3D.TriangleIndices.Add(0);
geometry3D.TriangleIndices.Add(2);
geometry3D.TriangleIndices.Add(2);
geometry3D.TriangleIndices.Add(3);
geometry3D.TriangleIndices.Add(1);
geometry3D.TextureCoordinates.Add(new Point(0, 0));
geometry3D.TextureCoordinates.Add(new Point(1, 0));
geometry3D.TextureCoordinates.Add(new Point(0, 1));
geometry3D.TextureCoordinates.Add(new Point(1, 1));
Material material = new DiffuseMaterial(GetBrush());
ModelVisual3D modelForGeometry = new ModelVisual3D { Content = new GeometryModel3D(geometry3D, material) };
mainViewPort3D.Children.Add(modelForGeometry);
backgroundGrid.Children.Add(mainViewPort3D);
}
private ImageBrush GetBrush()
{
// put any other image URI here, image Height = 466px, Width = 490px
ImageBrush brush = new ImageBrush(new BitmapImage(new Uri("lion.jpg", UriKind.Relative)));
brush.Stretch = Stretch.Fill;
return brush;
}
}
}
Thanks in advance for all your help!
There are some other variables to consider, then.
Your graphics card settings could be forcing the interpolation mode down despite WPF's request for something nicer looking. WPF's 3D is hardware accelerated on Tier 2 hardware, so check your drivers' control software. It might not be possible for WPF to request anything better!
Try enabling anti-aliasing in your application and graphics card settings, too.
Just guessing: you did not define any lights nor any normals. Sometimes that will cause a darker image than you would expect.
I have an image and i want to render the control(datgrid or any ui element with its content ) on that image and output the whole thing as an image.
Please Help.
I found some similar link in so, just taking a look at his answer but not helpful..
Silverlight: Create image from silverlight controls
Thanks in advance. :)
I used the following method I found somewhere to create an image from a frameworkelement (which is basically a grid, canvas, button, textbox, ...
it takes whatever it can find inside the bounds of the control and returns it as an ImageSource type, from there I think the road to saving it to a file or outputting it to the screen is a small step.
take note: I removed some code which should work out margin issues, so be sure to take that into account, or do not set a margin for the control you wish to convert to an image.
what you basically want to do now is use the GridCombiner and give that to the method below, so it will create an image from the DataGridMyData over the ImageBackground as an ImageSource.
Hopefully this is what you were looking for, if not, let me know.
public ImageSource ToImageSource(FrameworkElement obj) // FOR WPF
{
// Save current canvas transform
Transform transform = obj.LayoutTransform;
obj.LayoutTransform = null;
// Get the size of canvas
Size size = new Size(obj.Width, obj.Height);
// force control to Update
obj.Measure(size);
obj.Arrange(new Rect(size));
RenderTargetBitmap bmp = new RenderTargetBitmap(
(int)obj.Width, (int)obj.Height, 96, 96, PixelFormats.Pbgra32);
bmp.Render(obj);
// return values as they were before
obj.LayoutTransform = transform;
return bmp;
}
public ImageSource ToImageSource(FrameworkElement obj) // FOR SILVERLIGHT
{
// Save current canvas transform
Transform transform = obj.RenderTransform;
obj.RenderTransform = null;
// Get the size of canvas
Size size = new Size(obj.Width, obj.Height);
// force control to Update
obj.Measure(size);
obj.Arrange(new Rect(new Point(), size));
WriteableBitmap bmp = new WriteableBitmap(obj, transform);
bmp.Render(obj, transform);
// return values as they were before
obj.RenderTransform = transform;
return bmp;
}
And your xaml would be something like:
<Grid x:Name="GridCombiner" Width="300" Height="150">
<Image x:Name="ImageBackground" Source="c:/myimg.jpg" Width="300" Height="150" />
<DataGrid x:Name="DataGridMyData" ItemsSource="{Binding}" Width="300" Height="150" />
</Grid>