I have posted several questions related to this problem I am having and I am starting to believe this cannot be done. Here is the back story.
I have an ASP.NET application from which I want to generate a .png image. This .png image needs to be constructed from either XAML or a WPF Visual Tree. Because of this, I must generate the .png image in an STA thread. Everything works fine until my XAML/WPF Visual Tree includes an Image (as in a System.Windows.Controls.Image). My .png file gets generated correctly except the Image element does not show the referenced picture. The referenced picture is located at a remote URL. No errors or exceptions are thrown.
How do I create a .png image from some XAML/WPF Visual Tree that includes a System.Windows.Controls.Image element? The resulting .png must include the picture referenced in the Image element. I have tried the following code in a variety of ways:
string address = "http://imgtops.sourceforge.net/bakeoff/o-png24.png";
WebClient webClient = new WebClient();
byte[] imageContent = webClient.DownloadData(address);
Image image = new Image();
using (MemoryStream memoryStream = new MemoryStream(imageContent))
{
BitmapImage imageSource = new BitmapImage();
imageSource.BeginInit();
imageSource.StreamSource = memoryStream;
imageSource.EndInit();
image.Source = imageSource;
}
// Set the size
image.Height = 200;
image.Width = 300;
// Position the Image within a Canvas
image.SetValue(Canvas.TopProperty, 1.0);
image.SetValue(Canvas.LeftProperty, 1.0);
Canvas canvas = new Canvas();
canvas.Height = 200;
canvas.Width = 300;
canvas.Background = new SolidColorBrush(Colors.Purple);
canvas.Children.Add(image);
// Create the area
Size availableSize = new Size(300, 200);
frameworkElement.Measure(availableSize);
frameworkElement.Arrange(new Rect(availableSize));
// Convert the WPF representation to a PNG file
BitmapSource bitmap = RenderToBitmap(frameworkElement);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
// Generate the .png
FileStream fileStream = new FileStream(filename, FileMode.Create);
encoder.Save(fileStream);
public BitmapSource RenderToBitmap(FrameworkElement target)
{
int actualWidth = 300;
int actualHeight = 200;
Rect boundary = VisualTreeHelper.GetDescendantBounds(target);
RenderTargetBitmap renderBitmap = new RenderTargetBitmap(actualWidth, actualHeight, 96, 96, PixelFormats.Pbgra32);
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext context = drawingVisual.RenderOpen())
{
VisualBrush visualBrush = new VisualBrush(target);
context.DrawRectangle(visualBrush, null, new Rect(new Point(), boundary.Size));
}
renderBitmap.Render(drawingVisual);
return renderBitmap;
}
Thank you for your help.
You are rendering the output bitmap correctly, it is just the input bitmap you are screwwing up :).
BitmapImage requires access to the StreamSource property until it fires the DownloadCompleted event, but the 'using' block Dispose()s of the MemoryStream before it has a chance! You could simply unwrap the MemoryStream from the using block and let the GC handle it (if you do, I would recommend setting the BitmapImage.CacheOption to BitmapCacheOption.None, so it uses the stream directly, rather than a copy), but I would use the UriSource property and wait for the DownloadComplete event before rendering.
Related
I'm doing an image viewer (in a screen saver so images change randomly) in WPF and i would like to rotate a picture and save it at the same time (so it's rotated and i dont need to bother anymore).
I'm using Doubleanimation to fade one picture to the next.
Here is the function to rotate the image :
private void RotateImage(string path, float rotationAngle)
{
BitmapImage CurrentImage = new BitmapImage();
TransformedBitmap newTransformedImage = new TransformedBitmap();
Image img = new Image();
CurrentImage.BeginInit();
CurrentImage.UriSource = new Uri(path);
CurrentImage.EndInit();
newTransformedImage.BeginInit();
newTransformedImage.Source = CurrentImage;
RotateTransform transform = new RotateTransform(90);
newTransformedImage.Transform = transform;
newTransformedImage.EndInit();
img.Source = newTransformedImage;
Savetojpg(img, path);
}
and here is the funtion to save:
private void Savetojpg(Image image, string filePath)
{
var encoder = new JpegBitmapEncoder();
RenderTargetBitmap bitmap = new RenderTargetBitmap((int)image.ActualWidth, (int)image.ActualHeight, 96, 96, PixelFormats.Pbgra32);
Size visualSize = new Size(image.ActualWidth, image.ActualHeight);
image.Measure(visualSize);
image.Arrange(new Rect(visualSize));
bitmap.Render(image);
BitmapFrame frame = BitmapFrame.Create(bitmap);
encoder.QualityLevel = 100;
encoder.Frames.Add(frame);
using (var stream = File.Create(filePath))
{
encoder.Save(stream);
}
}
When i run this, i have a System.ArgumentOutOfRangeException at the first line of the Savetojpg function as it seems the image i'm sending to that function is empty.
Can anyone tell me what i am doing wrong ?
I'm developing an application and I would like to implement a feature similar to Microsoft Power Point's paging system where there is a smaller image of the sheets you are working on on the side.
I have the following function which renders an image of the Grid in which the user has to work.
private void CreateImageOfPage()
{
int width = (int)this.WorkSheetViewModelList.Last().RootGrid.Width;
int height = (int)this.WorkSheetViewModelList.Last().RootGrid.Height;
RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
renderTargetBitmap.Render(this.WorkSheetViewModelList.Last().RootGrid);
PngBitmapEncoder pngImage = new PngBitmapEncoder();
pngImage.Frames.Add(BitmapFrame.Create(renderTargetBitmap));
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderTargetBitmap));
Guid photoID = System.Guid.NewGuid();
String photolocation = "Temp/"+photoID.ToString() + ".png";
using (var filestream = new FileStream(photolocation, FileMode.Create))
encoder.Save(filestream);
Console.WriteLine(Path.GetFullPath(photoID.ToString() + ".png"));
}
My question is, how to get, set or format the path of the saved image to be set as Source of an Image in a XAML? In its current form the path looks like this : "D:\Project\bin\Debug\7fd77420-f62e-44e7-b206-b5ce19f01a9e.png", and it says it could not get the file at this location(for now I just copied the output in, but it will be bound, as it should be).
If the path I've taken is not elegant, is there any other way to do it better, or without saving the image to the disk at all?
Thanks!
You don't need to do any image encoding.
Assuming that there is an Image element named image, you could directly assign the RenderTargetBitmap to its Source property like this:
image.Source = renderTargetBitmap;
You may change your method to this:
private ImageSource CreateImageOfPage()
{
var rootGrid = WorkSheetViewModelList.Last().RootGrid;
var width = (int)rootGrid.ActualWidth;
var height = (int)rootGrid.ActualHeight;
var renderTargetBitmap = new RenderTargetBitmap(
width, height, 96, 96, PixelFormats.Pbgra32);
renderTargetBitmap.Render(rootGrid);
return renderTargetBitmap;
}
and use it like this:
image.Source = CreateImageOfPage();
I have a WPF application using InkCanvas. When I render the bitmap, save to a memory stream, write the resulting bytes to a file, and then open that file in paint, the image is mangled. Any idea what I may be doing wrong here? Tried several solutions found here on SO and also on codeproject. It's pretty clear that it's capturing part of the InkCanvas but the majority of it is black (I assume null bytes).
EDIT: also tried with/without margin. Here are the other links I've tried:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/ef71237c-5dfb-4d6c-a402-e8cb02b74e99/how-to-convert-inkcanvas-strokes-to-a-bitmap-or-byte-array?forum=wpf
Converting InkCanvas Strokes to a Byte Array and back again
InkCanvas Load/Save operations
http://www.centrolutions.com/Blog/post/2008/12/09/Convert-WPF-InkCanvas-to-Bitmap.aspx
https://social.msdn.microsoft.com/Forums/vstudio/en-US/ba4dc89f-0169-43a9-8374-68e1fb34a222/saving-inkcanvas-as-image?forum=wpf
I need the resultant file to be a bitmap/PNG so it can be viewed on another machine.
private byte[] ConvertInkCanvasToByteArray()
{
int margin = (int)this.icSignature.Margin.Left;
int width = (int)this.icSignature.ActualWidth - margin;
int height = (int)this.icSignature.ActualHeight - margin;
RenderTargetBitmap rtb = new RenderTargetBitmap(width, height, 96d, 96d, PixelFormats.Default);
rtb.Render(icSignature);
BmpBitmapEncoder encoder = new BmpBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(rtb));
byte[] bitmapBytes;
using (MemoryStream ms = new MemoryStream())
{
encoder.Save(ms);
ms.Position = 0;
bitmapBytes = ms.ToArray();
}
return bitmapBytes;
}
From the InkCanvas:
And then mangled:
To avoid any problem with the InkCanvas' Margin, you could draw it into an intermediate DrawingVisual:
private byte[] ConvertInkCanvasToByteArray()
{
var rect = new Rect(icSignature.RenderSize);
var visual = new DrawingVisual();
using (var dc = visual.RenderOpen())
{
dc.DrawRectangle(new VisualBrush(icSignature), null, rect);
}
var rtb = new RenderTargetBitmap(
(int)rect.Width, (int)rect.Height, 96d, 96d, PixelFormats.Default);
rtb.Render(visual);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(rtb));
using (var stream = new MemoryStream())
{
encoder.Save(stream);
return stream.ToArray();
}
}
I'm using Xceed WPF Toolkit (Community Edition) DataGridControl, and I would like to create a bitmap from the control (either to put on the clipboard or save to a png).
I have tried using a RenderBitmapTarget, but it will only copy the control as it is rendered on the screen (my grid is bigger than the screen).
My RenderBitmapTarget code looks like this:
RenderTargetBitmap rtb = new RenderTargetBitmap((int)control.ActualWidth, (int)control.ActualHeight, 96, 96, PixelFormats.Pbgra32);
rtb.Render(control);
PngBitmapEncoder png = new PngBitmapEncoder();
png.Frames.Add(BitmapFrame.Create(rtb));
MemoryStream stream = new MemoryStream();
png.Save(stream);
Image image = Image.FromStream(stream);
I've tried specifying a larger size (both in the RenderTargetBitmap constructor, and specifying new width/height for the control, but both just yielded the same image on a larger canvas.
Any thoughts?
Ok, I figured it out....
The final key was to disable delayed loading on the DataGridControl.View...here is my final code:
XAML:
<xcdg:DataGridControl x:Name="CEGrid">
<xcdg:DataGridControl.View>
<xcdg:TableflowView IsDeferredLoadingEnabled="False"/>
</xcdg:DataGridControl.View>
</xcdg:DataGridControl>
C# code-behind:
double tempWidth = CEGrid.ActualWidth;
double tempHeight = CEGrid.ActualHeight;
CEGrid.Width = double.NaN;
CEGrid.Height = double.NaN;
CEGrid.UpdateLayout();
RenderTargetBitmap rtb = new RenderTargetBitmap((int)CEGrid.ActualWidth, (int)CEGrid.ActualHeight, 96, 96, PixelFormats.Pbgra32);
rtb.Render(CEGrid);
PngBitmapEncoder pbe = new PngBitmapEncoder();
pbe.Frames.Add(BitmapFrame.Create(rtb));
MemoryStream stream = new MemoryStream();
pbe.Save(stream);
System.Drawing.Bitmap image = (System.Drawing.Bitmap)System.Drawing.Image.FromStream(stream);
CEGrid.Width = tempWidth;
CEGrid.Height = tempHeight;
I have a StackPanel which includes a few Rectangles that I want put to an image file (e.g. PNG). I'm developing this on Windows Phone 7 and most of the information I found on the internet wasn't applicable (I think) to WP7.
I think the System.Windows.Media.Imaging namespace is the key to this, but I'm not sure where to begin.
This is basically what I want to do:
StackPanel stack = new StackPanel();
List<Rectangle> recList = new List<Rectangle>();
add some rectangles to recList
foreach(var x in recList)
stack.Children.Add(x);
then save the stackpanel to an image file...
You can use a WriteableBitmap to save the image.
WriteableBitmap wb = new WriteableBitmap(stack, null);
MemoryStream ms = new MemoryStream();
wb.SaveJpeg(ms, myWidth, myHeight, 0, 100);
You can change the MemoryStream to be an Isolated Storage stream instead. If you want to display the above MemoryStream in an Image control:
BitmapImage bmp = new BitmapImage();
bmp.SetSource(ms);
image1.Source = bmp;
Or, saving to Isolated Storage:
using (var isoFileStream = new IsolatedStorageFileStream("myPicture.jpg", FileMode.OpenOrCreate, IsolatedStorageFile.GetUserStoreForApplication()))
{
wb.SaveJpeg(isoFileStream, myWidth, myHeight, 0, 100);
}