I am applying a blur in a background thread to increase performance. This function is returning a RenderTargetBitmap. When this is done I'm invoking through the Dispatcher an update on the image and add it as content to the page. This is done like the following:
System.Windows.Controls.Image image = new System.Windows.Controls.Image();
Thread screenshotThread = new Thread(new ThreadStart(delegate()
{
RenderTargetBitmap img = CaptureScreen(0, 0, actualWidth, actualHeight);
//System.Windows.Controls.Image image = imgBlur;
//image = new System.Windows.Controls.Image();
Application.Current.Dispatcher.Invoke(() =>
{
image.Source = img;
image.Width = actualWidth;
image.Height = actualHeight;
PageContainer.Children.Add(image);
});
}));
screenshotThread.SetApartmentState(ApartmentState.STA);
screenshotThread.Start();
I'm adding the image to the PageContainer, this is a Grid. After running this piece of code, the image has been added to the page. However, the imagesource is null.. No image is currently visible. How can I make this image appear?
You have created the Imagecontrol and the RenderTargetBitmap on different thread. I am surprised you did not get an exception. Try adding img.Freeze(); before setting it to the Image.Source.
Related
I am loading an image into an ink canvas, the input image is always monochromatic, I am then drawing on that image with a white pen and intending to save it.
When the image is loaded some of the pre-existing lines which I know to be 1 pixel thick have an edge added to them which isn't monochromatic.
The way I have though to fix this is by rendering the bitmap and then discarding all pixels with a value of less than 255.
I have tried to use the pixel format BlackWhite, however this generates the error:
An unhandled exception of type 'System.ArgumentException' occurred in PresentationCore.dll
Additional information: 'BlackWhite' PixelFormat is not supported for this operation.
The line of code rendering the bitmap
RenderTargetBitmap rtb = new RenderTargetBitmap((int)inkCanvas.ActualWidth, (int)inkCanvas.ActualHeight, 96, 96, System.Windows.Media.PixelFormats.BlackWhite);
I'm not sure if the issue lies in how I loaded it into the ink canvas so that code is also included below
private void LoadImagetoCanvas(object sender, RoutedEventArgs e)
{
Microsoft.Win32.OpenFileDialog openFileDlg = new Microsoft.Win32.OpenFileDialog();
Nullable<bool> result = openFileDlg.ShowDialog();
if (result == true)
{
global.canvas1filepath = openFileDlg.FileName;
System.Windows.Controls.Image myImage = new System.Windows.Controls.Image();
myImage.Source = new BitmapImage(new Uri(global.canvas1filepath));
BitmapImage bmp = new BitmapImage(new Uri(global.canvas1filepath, UriKind.Absolute));
global.canvas1imagexpixels = (int)bmp.Width;
global.canvas1imageypixels = (int)bmp.Height;
ImageBrush canvas1Background = new ImageBrush();
canvas1Background.ImageSource = new BitmapImage(new Uri(global.canvas1filepath, UriKind.Relative));
inkCanvas1.Background = canvas1Background;
}
}
I'll compile an answer here - thanks to Sinatr & Clemens for their help with the theory and examples
The solution is based in the XMAL code for the ink canvas i'm using, I found adding the two following properties removed any edges to the 1 pixel wide lines I was drawing:
RenderOptions.BitmapScalingMode="NearestNeighbor" RenderOptions.EdgeMode="Aliased"
I'm trying to do the following:
Create a Livechart Cartesian chart in memory
Add the chart to a grid
Add Labels to the same grid
Add the grid to a Viewbox
Render the Viewbox as a PNG
Save the PNG to disk
The above should be run from a different thread in the background in order to allow UI reponsiveness.
However simple this may seem, I've been struggling to get a proper working solution. The following issues are relevant:
The Livechart (which is inside the Viewbox) takes time to render
Thus the chart needs to be given time to complete rendering before trying to save it as an image
I have found code which makes use of HwndSource, however it is not working all the time (works about 95% of the time). Without the HwndSource modification it NEVER works (always gets a chart with nothing on it)
Running the Run() function in a different UI thread does not work, as I get the following error message: WPF Dispatcher {“The calling thread cannot access this object because a different thread owns it.”}
So my questions are:
What is the right way to wait for the Livechart/Grid/ViewBox combination to finish rendering before saving it as an image? Maybe make use of the Loaded event? Note that I have tried to impelment it but cannot get it to work as I hit the 'threading' issue.
How can I run the entire process in a different UI thread?
See below for code
public void Run()
(
//Create Livechart which is a child of a Grid control
Grid gridChart = Charts.CreateChart();
//Creates a ViewBox control which has the grid as its child
Viewbox viewBox = WrapChart(gridChart,1400,700);
//Creates and saves the image
CreateAndSaveImage(viewBox ,path,name);
)
Below is the function which creates the Viewbox and add the grid as a child
public Viewbox viewBox WrapChart(Grid grid,int width,int height)
{
chart.grid.Width = width;
chart.grid.Height = height;
viewbox.Child = chart.grid;
viewbox.Width = width;
viewbox.Height = height;
viewbox.Measure(new System.Windows.Size(width, height));
viewbox.Arrange(new Rect(0, 0, width, height));
viewbox.UpdateLayout();
}
Function below creates and saves the image
public void CreateAndSaveImage(Viewbox viewbox,string folderPath,string fileName)
{
var x = HelperFunctions.GetImage(viewbox);
System.IO.FileStream stream = System.IO.File.Create(folderPath + fileName);
HelperFunctions.SaveAsPng(x, stream);
stream.Close();
}
The following code renders the viewbox to an image. Note that this is the only code that I could find which waits for the chart to finish loading. I have no idea how it works, but it works 95% of the time. Sometimes a chart still does not finish loading.
public static RenderTargetBitmap GetImage(Viewbox view)
{
using (new HwndSource(new HwndSourceParameters())
{
RootVisual =
(VisualTreeHelper.GetParent(view) == null
? view
: null)
})
{
Size size = new Size(view.ActualWidth, view.ActualHeight);
if (size.IsEmpty)
return null;
int actualWidth = Convert.ToInt32(size.Width);
int requiredWidth = Convert.ToInt32(size.Width * 1);
int actualHeight = Convert.ToInt32(size.Height);
int requiredHeight = Convert.ToInt32(size.Height * 1);
// Flush the dispatcher queue
view.Dispatcher.Invoke(DispatcherPriority.SystemIdle, new Action(() => { }));
var renderBitmap = new RenderTargetBitmap(requiredWidth, requiredHeight,
96d * requiredWidth / actualWidth, 96d * requiredHeight / actualHeight,
PixelFormats.Pbgra32);
DrawingVisual drawingvisual = new DrawingVisual();
using (DrawingContext context = drawingvisual.RenderOpen())
{
context.DrawRectangle(new VisualBrush(view), null, new Rect(new Point(), size));
context.Close();
}
renderBitmap.Render(view);
renderBitmap.Freeze();
return renderBitmap;
}
}
The following code saves the bitmap as a picture to file
public static void SaveAsPng(BitmapSource src, Stream outputStream)
{
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(src));
encoder.Save(outputStream);
}
The following code is what I use to run the entire thing in a different thread. Note that it is not working, as I get the following error message:
WPF Dispatcher {“The calling thread cannot access this object because
a different thread owns it.”}.
Note that if I execute Run() normally (without any separate threads) it works, however sometimes the chart does not render properly (as explained previously).
Thread thread = new Thread(() =>
{
Run();
System.Windows.Threading.Dispatcher.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
Try call this line for the chart:
this.chart.Model.Updater.Run(false, true);
This line updates the chart and always is visible when save to image.
I am currently converting a Silverlight application into WPF. In my silverlight application I have the code
WriteableBitmap sceneBitmap = new WriteableBitmap(scene, new TranslateTransform() { Y = 10 });
WriteableBitmap newone = TimelineMainHelper.CropImage(sceneBitmap, 0, 0, sceneBitmap.PixelWidth, sceneBitmap.PixelHeight - 25);
newone.Invalidate();
img.Source = newone;
Where scene is a control.
When putting this into WPF there are no overloads for the writeablebitmap class which take UIElement and Transform as the parameters. Firstly I was wondering why this is? and secondly I was wondering if there was a way getting a control to a writeablebitmap
Instead you will want to use RenderTargetBitmap and CroppedBitmap I believe:
RenderTargetBitmap rtb = new RenderTargetBitmap((int)scene.ActualWidth, (int)scene.ActualHeight, 96, 96, System.Windows.Media.PixelFormats.Pbgra32);
rtb.Render(this.sceneBitmap);
CroppedBitmap crop = new CroppedBitmap(sceneBitmap, new Int32Rect(0, 0, (int)sceneBitmap.ActualWidth, (int)sceneBitmap.ActualHeight));
Then you can do something like:
System.Windows.Controls.Image img = new Image();
img.Source = crop;
And go from there.
Disclaimer:
You may need to use different overloads and what not to do exactly what you wish. I just took a shot guessing what parameters to pass given your snippet.
Rather than declaring an image and setting the source from the xaml file, can someone do the initialization part, set the image coordinates, and set the source completely in the code?
// Create Image Element
Image myImage = new Image();
myImage.Width = 200;
// Create source
BitmapImage myBitmapImage = new BitmapImage();
// BitmapImage.UriSource must be in a BeginInit/EndInit block
myBitmapImage.BeginInit();
myBitmapImage.UriSource = new Uri(#"C:\Documents and Settings\All Users\Documents\My Pictures\Sample Pictures\Water Lilies.jpg");
// To save significant application memory, set the DecodePixelWidth or
// DecodePixelHeight of the BitmapImage value of the image source to the desired
// height or width of the rendered image. If you don't do this, the application will
// cache the image as though it were rendered as its normal size rather then just
// the size that is displayed.
// Note: In order to preserve aspect ratio, set DecodePixelWidth
// or DecodePixelHeight but not both.
myBitmapImage.DecodePixelWidth = 200;
myBitmapImage.EndInit();
//set image source
myImage.Source = myBitmapImage;
You need to create a new Image specifying the source:
Image myImage = new Image();
BitmapImage bitmapImage = new BitmapImage(new Uri("/YourSource", UriKind.Relative)); //Or UriKind.Absolute depending in the path
myImage.Source = bitmapImage;
If you want to place the image into some coordenates you can place a Canvas behind and place the image using Canvas coordenates. Use:
_myCanvas.Children.Add(myImage); //To add your image to Canvas, declared on Xaml or previously created and added to your control
Canvas.SetTop(myImage, 100); //Set Y coordenate relative to Canvas initial point
Canvas.SetLeft(myImage, 100); // Set X
I'm writing an application for the surface that requires displaying data in a table (i.e. DataGrid). This is great, except the table captures the touch interactions for the ScatterViewItem control (basically a panel that can be spun, shrunk, and moved by the user). This prevents the user from easily manipulating the ScatterViewItem.
To solve this problem, I thought it would be easy to draw the control to an image and just put that up. It seems I was wrong. Here are all my attempts:
http://pastie.org/private/gfkkv9f6apgrqi1ucspwpa (no need to read this, unless you think it will be useful. That's why it's in pastie and not on here)
I'm putting the DataGrid inside of another Grid, because otherwise it won't measure properly:
Grid g = new Grid();
g.Children.Add(dataTable);
SurfaceScrollViewer viewer = new SurfaceScrollViewer();
viewer.Content = Utility.SaveWPFControlAsImage(g);
If we change that last line to
viewer.Content = g;
We get a good table:
If we don't, we get:
SaveWPFControlAsImage is as follows:
public static System.Windows.Controls.Image SaveWPFControlAsImage(FrameworkElement e)
{
e.Measure(new System.Windows.Size(double.PositiveInfinity, double.PositiveInfinity));
RenderTargetBitmap targetBitmap =
new RenderTargetBitmap((int)e.DesiredSize.Width,
(int)e.DesiredSize.Height,
96d, 96d,
PixelFormats.Default);
targetBitmap.Render(e);
BmpBitmapEncoder encoder = new BmpBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(targetBitmap));
MemoryStream stream = new MemoryStream();
encoder.Save(stream);
BitmapImage bmp = new BitmapImage();
bmp.BeginInit();
bmp.StreamSource = new MemoryStream(stream.ToArray());
bmp.EndInit();
return new System.Windows.Controls.Image()
{
Source = bmp,
};
}
So maybe I'm just not rendering it right, or, maybe, I'm just going about this at the wrong angle...
In WPF you have a VisualBrush which allows you to capture a live preview of a given control. Also if you don't want any input of a given control, you can always set IsHitTestVisible="False".