I am just looking around and tried to making a working version of saving multiple canvas as one image. For example would be nice to save them nearby each other, but I can not figure out how to do it.
public static void CreateSaveBitmap(Canvas canvas1, Canvas canvas2, string filename)
{
RenderTargetBitmap renderBitmap1 = new RenderTargetBitmap((int)canvas1.ActualWidth, (int)canvas1.ActualWidth, 96d, 96d, PixelFormats.Pbgra32);
// needed otherwise the image output is black
canvas1.Measure(new Size((int)canvas1.ActualWidth, (int)canvas1.ActualWidth));
canvas1.Arrange(new Rect(new Size((int)canvas1.ActualWidth, (int)canvas1.ActualWidth)));
renderBitmap1.Render(canvas1);
RenderTargetBitmap renderBitmap2 = new RenderTargetBitmap((int)canvas2.ActualWidth, (int)canvas2.ActualWidth, 96d, 96d, PixelFormats.Pbgra32);
// needed otherwise the image output is black
canvas2.Measure(new Size((int)canvas2.ActualWidth, (int)canvas2.ActualWidth));
canvas2.Arrange(new Rect(new Size((int)canvas2.ActualWidth, (int)canvas2.ActualWidth)));
renderBitmap2.Render(canvas2);
//JpegBitmapEncoder encoder = new JpegBitmapEncoder();
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderBitmap1));
encoder.Frames.Add(BitmapFrame.Create(renderBitmap2));
using (FileStream file = File.Create(filename))
{
encoder.Save(file);
}
}
This is how I tried, but its only saving the first image. Can someone help me to solve this please?
UPDATE
I corrected some mistakes what told by #TheLethalCoder, and I also changed the output filetype to Tiff, but it doesn't helped. This is the latest version now:
public static void CreateSaveBitmap(Canvas canvas1, Canvas canvas2, string filename)
{
RenderTargetBitmap renderBitmap1 = new RenderTargetBitmap((int)canvas1.ActualWidth, (int)canvas1.ActualHeight, 96d, 96d, PixelFormats.Pbgra32);
// needed otherwise the image output is black
canvas1.Measure(new Size((int)canvas1.ActualWidth, (int)canvas1.ActualHeight));
canvas1.Arrange(new Rect(new Size((int)canvas1.ActualWidth, (int)canvas1.ActualHeight)));
renderBitmap1.Render(canvas1);
RenderTargetBitmap renderBitmap2 = new RenderTargetBitmap((int)canvas2.ActualWidth, (int)canvas2.ActualHeight, 96d, 96d, PixelFormats.Pbgra32);
// needed otherwise the image output is black
canvas2.Measure(new Size((int)canvas2.ActualWidth, (int)canvas2.ActualHeight));
canvas2.Arrange(new Rect(new Size((int)canvas2.ActualWidth, (int)canvas2.ActualHeight)));
renderBitmap2.Render(canvas2);
//JpegBitmapEncoder encoder = new JpegBitmapEncoder();
TiffBitmapEncoder encoder = new TiffBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderBitmap1));
encoder.Frames.Add(BitmapFrame.Create(renderBitmap2));
using (FileStream file = File.Create(filename))
{
encoder.Save(file);
}
}
What you want is not multiple-frame image but to combine the images of your canvases by juxtaposing them. So actually you need just 1 Frame here but a big combined one. There are some ways to combine them but there is 1 simple way you can do with DrawingVisual. We can draw a Drawing on that visual and use RenderTargetBitmap to capture it. We use DrawingGroup to combine the ImageDrawings (from the 2 RenderTargetBitmaps). In summary, we have to do the following steps: from 2 RenderTargetBitmaps, you put them in 2 ImageDrawings and combine those into a DrawingGroup. Then you draw that combined Drawing on a DrawingVisual. Finally use RenderTargetBitmap to render that visual normally.
Here is the code you can try out:
public static void CreateSaveBitmap(Canvas canvas1, Canvas canvas2, string filename)
{
RenderTargetBitmap renderBitmap1 = new RenderTargetBitmap((int)canvas1.ActualWidth, (int)canvas1.ActualHeight, 96d, 96d, PixelFormats.Pbgra32);
canvas1.Measure(new Size((int)canvas1.ActualWidth, (int)canvas1.ActualHeight));
canvas1.Arrange(new Rect(new Size((int)canvas1.ActualWidth, (int)canvas1.ActualHeight)));
renderBitmap1.Render(canvas1);
RenderTargetBitmap renderBitmap2 = new RenderTargetBitmap((int)canvas2.ActualWidth, (int)canvas2.ActualHeight, 96d, 96d, PixelFormats.Pbgra32);
canvas2.Measure(new Size((int)canvas2.ActualWidth, (int)canvas2.ActualHeight));
canvas2.Arrange(new Rect(new Size((int)canvas2.ActualWidth, (int)canvas2.ActualHeight)));
renderBitmap2.Render(canvas2);
//Combine the images here
var dg = new DrawingGroup();
var id1 = new ImageDrawing(renderBitmap1,
new Rect(0,0,renderBitmap1.Width, renderBitmap1.Height));
var id2 = new ImageDrawing(renderBitmap2,
new Rect(renderBitmap1.Width, 0
renderBitmap2.Width,
renderBitmap2.Height));
dg.Children.Add(id1);
dg.Children.Add(id2);
var combinedImg = new RenderTargetBitmap((int)(renderBitmap1.Width + renderBitmap2.Width + 0.5),
(int)(Math.Max(renderBitmap1.Height, renderBitmap2.Height) + 0.5), 96, 96, PixelFormats.Pbgra32);
var dv = new DrawingVisual();
using(var dc = dv.RenderOpen()){
dc.DrawDrawing(dg);
}
combinedImg.Render(dv);
//JpegBitmapEncoder encoder = new JpegBitmapEncoder();
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(combinedImg));
using (FileStream file = File.Create(filename)) {
encoder.Save(file);
}
}
Related
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;
}
I have a DVC Chart displaying data. I have created a button to export the graph.
Here is the code:
private void btnExptGraph_Click(object sender, RoutedEventArgs e)
{
RenderTargetBitmap renderBitmap = new RenderTargetBitmap((int)mcChart.ActualWidth, (int)mcChart.ActualHeight,96d, 96d, PixelFormats.Pbgra32);
renderBitmap.Render(mcChart);
Console.WriteLine(renderBitmap.ToString());
//JpegBitmapEncoder encode = new JpegBitmapEncoder();
PngBitmapEncoder encode = new PngBitmapEncoder();
encode.Frames.Add(BitmapFrame.Create(renderBitmap));
string filename = "test.bmp";
FileStream fout = new FileStream(filename, FileMode.Create);
encode.Save(fout);
MessageBox.Show("File Saved Successfully");
fout.Close();
}
So my issue is that this executes properly with the exception that my file is basically empty. I only have a file of 1KB in size and no graph.
I've looked at the MSDN documentation and other stack overflow examples. All of them follow this form and people claim that it works. I've run with a debugger and the renderBitmap object is getting the correct height and weight values in all necessary properties. Any ideas?
My friend took a look at it and this was the solution that we came up with and does work:
Size size = new Size(mcChart.ActualWidth, mcChart.ActualHeight);
if (size.IsEmpty)
return;
size.Height *= 2;
size.Width *= 2;
RenderTargetBitmap result = new RenderTargetBitmap((int)size.Width, (int)size.Height, 96, 96, PixelFormats.Pbgra32);
DrawingVisual drawingvisual = new DrawingVisual();
using (DrawingContext context = drawingvisual.RenderOpen())
{
context.DrawRectangle(new VisualBrush(mcChart), null, new Rect(new Point(), size));
context.Close();
}
result.Render(drawingvisual);
string filename = "test.png";
FileStream fout = new FileStream(filename, FileMode.Create);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(result));
encoder.Save(fout);
fout.Close();
MessageBox.Show("Done");
I need to make image from canvas and from internet I find this code who works perfect. Is make the image and is good. But now I need to add some text in this new image and i think this is the problem. Sometimes is working, sometimes is throw me this exception. How can I close the image and change the image again. Or how can I change the image without is have exception.
int width = (int)(SystemParameters.PrimaryScreenWidth * 0.75);
int height = (int)canvas.Height;
RenderTargetBitmap renderBitmap = new RenderTargetBitmap(
width, (int)canvas.Height, 96d, 96d, PixelFormats.Pbgra32);
// needed otherwise the image output is black
canvas.Measure(new System.Windows.Size(width, height));
canvas.Arrange(new Rect(new System.Windows.Size(width, height)));
renderBitmap.Render(canvas);
//JpegBitmapEncoder encoder = new JpegBitmapEncoder();
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
using (FileStream file = File.Create(filename))
{
encoder.Save(file);
}
This is what Exception I have:
The process cannot access the file 'image.png' because it is being used by another process.
I try with file.Close(), I try without file.Close() filename is path to image file who create and then used and try delete
Here and my code This code is running every 1 second.
private void CreateSaveBitmap(Canvas canvas, string filename, string sensor)
{
int width = (int)(SystemParameters.PrimaryScreenWidth * 0.75);
int height = (int)canvas.Height;
RenderTargetBitmap renderBitmap = new RenderTargetBitmap(
width, (int)canvas.Height,
96d, 96d, PixelFormats.Pbgra32);
// needed otherwise the image output is black
canvas.Measure(new System.Windows.Size(width, height));
canvas.Arrange(new Rect(new System.Windows.Size(width, height)));
renderBitmap.Render(canvas);
//JpegBitmapEncoder encoder = new JpegBitmapEncoder();
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
using (FileStream file = File.Create(filename))
{
encoder.Save(file);
file.Close();
}
string head = SetHeadForPrintImage(sensor);
Bitmap bmp = new Bitmap(filename);
Graphics g = Graphics.FromImage(bmp);
g.DrawString(head, new System.Drawing.Font("Tahoma", 12f, System.Drawing.FontStyle.Regular), System.Drawing.Brushes.Black, new PointF(200, 0));
bmp.SetResolution(300, 300);
File.Delete(filename); // throw me exception
File.Delete(#".\Print\" + sensor + ".jpg");
bmp.Save(#".\Print\" + sensor + ".jpg");
}
private void CreateSaveBitmap(Canvas canvas, string filename, string sensor)
{
if(File.Exists(#".\Print\" + sensor + ".jpg"))
{
File.Delete(#".\Print\" + sensor + ".jpg");
}
int width = (int)(SystemParameters.PrimaryScreenWidth * 0.75);
int height = (int)canvas.Height;
RenderTargetBitmap renderBitmap = new RenderTargetBitmap(
width, (int)canvas.Height,
96d, 96d, PixelFormats.Pbgra32);
// needed otherwise the image output is black
canvas.Measure(new System.Windows.Size(width, height));
canvas.Arrange(new Rect(new System.Windows.Size(width, height)));
renderBitmap.Render(canvas);
//JpegBitmapEncoder encoder = new JpegBitmapEncoder();
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
using (FileStream file = File.Create(filename))
{
encoder.Save(file);
file.Close();
}
string head = SetHeadForPrintImage(sensor);
Bitmap bmp = new Bitmap(filename);
Graphics g = Graphics.FromImage(bmp);
g.DrawString(head, new System.Drawing.Font("Tahoma", 12f, System.Drawing.FontStyle.Regular), System.Drawing.Brushes.Black, new PointF(200, 0));
bmp.SetResolution(300, 300);
bmp.Save(#".\Print\" + sensor + ".jpg");
bmp.Dispose();
File.Delete(filename);
}
Bitmap objects often hold a reference to the file that they are read from , and the file may not be deleted while the bitmap is open (this differs depending on which file format the image is; it may be safe deleting the origninal file of a png but not jpg etc).
You should always call Dispose() on objects that are IDisposable.
The best thing would be if you could put the bmp object in a using statement
I have a WPF control in my PowerPoint add-in that hosts an image that I want to be able to drag & drop onto the active slide. I can get the image to appear on the slide, but the transparent areas are rendered in black.
My code to initialize the drag from my attached behavior:
var targetBitmap = new RenderTargetBitmap(
(int) MyWpfControl.ActualWidth,
(int) MyWpfControl.ActualHeight,
96d, 96d, PixelFormats.Default);
targetBitmap.Render(MyWpfControl);
var dataObject = new DataObject(
DataFormats.Bitmap,
targetBitmap);
DragDrop.DoDragDrop(MyWpfControl, dataObject, DragDropEffects.Copy)
Thinking that maybe I needed to pass a System.Drawing.Image, I attempted this modification, which only resulted in the transparent areas being rendered in gray:
var targetBitmap = new RenderTargetBitmap(
(int) MyWpfControl.ActualWidth,
(int) MyWpfControl.ActualHeight,
96d, 96d, PixelFormats.Default);
targetBitmap.Render(MyWpfControl);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(targetBitmap));
var ms = new MemoryStream();
encoder.Save(ms);
var dataObject = new DataObject(DataFormats.Bitmap, Image.FromStream(ms, true))
DragDrop.DoDragDrop(MyWpfControl, dataObject, DragDropEffects.Copy)
I did a test where I replaced the memory stream with a file stream, and the image that was written did indeed have the correct transparency.
So what am I missing here? How can I maintain transparency?
I was able to resolve this by following the instructions in this blog post. The solution was to use the EnhancedMetafile DataFormat in my DataObject.
Edit:
Here’s the code that initiates the drag operation.
private void Image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Bitmap bitmap = ImageToBitmap(e.Source as System.Windows.Controls.Image);
DataObject data = new DataObject(DataFormats.EnhancedMetafile, MakeMetafileStream(bitmap));
DragDrop.DoDragDrop((DependencyObject)e.Source, data, DragDropEffects.Copy);
}
This makes use of a utility function to convert the Image to a Bitmap:
private Bitmap ImageToBitmap(System.Windows.Controls.Image image)
{
RenderTargetBitmap rtBmp = new RenderTargetBitmap((int)image.ActualWidth, (int)image.ActualHeight,
96.0, 96.0, PixelFormats.Pbgra32);
image.Measure(new System.Windows.Size((int)image.ActualWidth, (int)image.ActualHeight));
image.Arrange(new Rect(new System.Windows.Size((int)image.ActualWidth, (int)image.ActualHeight)));
rtBmp.Render(image);
PngBitmapEncoder encoder = new PngBitmapEncoder();
MemoryStream stream = new MemoryStream();
encoder.Frames.Add(BitmapFrame.Create(rtBmp));
// Save to memory stream and create Bitamp from stream
encoder.Save(stream);
return new System.Drawing.Bitmap(stream);
}
This also requires a utility function that converts a Bitmap to a stream containing a Metafile, taken from Stack Overflow.
// From Convert an image into WMF with .NET?
private MemoryStream MakeMetafileStream(Bitmap image)
{
Graphics graphics = null;
Metafile metafile = null;
var stream = new MemoryStream();
try
{
using (graphics = Graphics.FromImage(image))
{
var hdc = graphics.GetHdc();
metafile = new Metafile(stream, hdc);
graphics.ReleaseHdc(hdc);
}
using (graphics = Graphics.FromImage(metafile))
{ graphics.DrawImage(image, 0, 0); }
}
finally
{
if (graphics != null)
{ graphics.Dispose(); }
if (metafile != null)
{ metafile.Dispose(); }
}
return stream;
}
I want to draw large number of shapes (lines, ellipses and ...) and then save them as bitmap or png. I made the drawings and the question is: how can I convert a DrawingImage to BitmapImage in C#? the code is something like this:
DrawingGroup drawingGroup = new DrawingGroup();
using(DrawingContext context = drawingGroup.Open())
{
//make some drawing
}
DrawingImage drawingImage = new DrawingImage(drawingGroup)
// your suggestion? DrawingImage - > BitmapImage
You may put the ImageDrawing into an Image control and render that into a RenderTargetBitmap, which is a BitmapSource and can therefore be serialized by a BitmapEncoder (PngBitmapEncoder in this example).
public void SaveDrawingToFile(Drawing drawing, string fileName, double scale)
{
var drawingImage = new Image { Source = new DrawingImage(drawing) };
var width = drawing.Bounds.Width * scale;
var height = drawing.Bounds.Height * scale;
drawingImage.Arrange(new Rect(0, 0, width, height));
var bitmap = new RenderTargetBitmap((int)width, (int)height, 96, 96, PixelFormats.Pbgra32);
bitmap.Render(drawingImage);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
using (var stream = new FileStream(fileName, FileMode.Create))
{
encoder.Save(stream);
}
}
Note that you don't actually need a BitmapImage for encoding, because BitmapSource (or any derived class like RenderTargetBitmap) will be accepted as argument to BitmapFrame.Create.
A slightly different solution would involve a DrawingVisual instead of a DrawingImage:
public void SaveDrawingToFile(Drawing drawing, string fileName, double scale)
{
var drawingVisual = new DrawingVisual();
using (var drawingContext = drawingVisual.RenderOpen())
{
drawingContext.PushTransform(new ScaleTransform(scale, scale));
drawingContext.PushTransform(new TranslateTransform(-drawing.Bounds.X, -drawing.Bounds.Y));
drawingContext.DrawDrawing(drawing);
}
var width = drawing.Bounds.Width * scale;
var height = drawing.Bounds.Height * scale;
var bitmap = new RenderTargetBitmap((int)width, (int)height, 96, 96, PixelFormats.Pbgra32);
bitmap.Render(drawingVisual);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
using (var stream = new FileStream(fileName, FileMode.Create))
{
encoder.Save(stream);
}
}
I found it pretty easy this way:
public static BitmapSource ToBitmapSource(DrawingImage source)
{
DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
drawingContext.DrawImage(source, new Rect(new Point(0, 0), new Size(source.Width, source.Height)));
drawingContext.Close();
RenderTargetBitmap bmp = new RenderTargetBitmap((int)source.Width, (int)source.Height, 96, 96, PixelFormats.Pbgra32);
bmp.Render(drawingVisual);
return bmp;
}
You may use it to get System.Drawing.Bitmap
using (MemoryStream ms = new MemoryStream())
{
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(ToBitmapSource(drawingImage)));
encoder.Save(ms);
using (System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(ms))
{
bmpOut = new System.Drawing.Bitmap(bmp);
}
}