How can I capture an entire image from a canvas? - wpf

I am working on a project to number features on an image. Each item being numbered is a textblock object being printed on top of a canvas that has a jpg(or bmp/gif/png) drawn to it's background using an image brush.
When it comes time to save the graphic I want to have two options, one to save it as a new jpg (or other format) that has the textblock objects rendered into it. or option b which would be to save the placements of those textblocks to a seperate file (XML, XAML what have you) so you could come back and continue to edit the entries.
Here is some of my code.
This is where I am trying to save the image
private void miSaveImage_Click(object sender, RoutedEventArgs e)
{
ImageBrush bi = (ImageBrush)canvas.Background;
Rect bounds = VisualTreeHelper.GetDescendantBounds(canvas);
double pw = (bi.ImageSource as BitmapSource).PixelWidth;
double ph = (bi.ImageSource as BitmapSource).PixelHeight;
double px = (bi.ImageSource as BitmapSource).DpiX;
double py = (bi.ImageSource as BitmapSource).DpiY;
//Get the actual width and height of the image, then the dpi and render an image of it
RenderTargetBitmap rtb = new RenderTargetBitmap((int)pw, (int)ph, px*96, py*96, System.Windows.Media.PixelFormats.Default);
rtb.Render(canvas);
//create the encoder and save the image data to it.
BitmapEncoder pngEncoder = new PngBitmapEncoder();
pngEncoder.Frames.Add(BitmapFrame.Create(rtb));
try
{
//eventually I need to make this a save dialog but that will come later.
System.IO.MemoryStream ms = new System.IO.MemoryStream();
pngEncoder.Save(ms);
ms.Close();
System.IO.File.WriteAllBytes("Sample.png", ms.ToArray());
}
catch (Exception err)
{
MessageBox.Show(err.ToString(), "Problem saving image", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
This is the code I use to load the image
//Global members
Image i;
static Size _Size = new Size();
private void miLoadImage_Click(object sender, RoutedEventArgs e)
{
// Create OpenFileDialog
Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
// Set filter for file extension and default file extension
dlg.DefaultExt = ".png";
dlg.Filter = #"All supported graphics|*.jpg;*.jpeg;*.png;*.gif;*.bmp|
JPEG Files (*.jpeg)|*.jpeg|
PNG Files (*.png)|*.png|
JPG Files (*.jpg)|*.jpg|
GIF Files (*.gif)|*.gif|
BMP Files (*.bmp)|*.bmp";
// Display OpenFileDialog by calling ShowDialog method
Nullable<bool> result = dlg.ShowDialog();
// Get the selected file name and display in a TextBox
if (result == true)
{
// Open document
string filename = dlg.FileName;
Uri uri = new Uri(#dlg.FileName, UriKind.Relative);
//Use canvas rather than image
ImageBrush ib = new ImageBrush();
ib.ImageSource = new BitmapImage(new Uri(filename, UriKind.Relative));
canvas.Background = ib;
if (i == null)
i = new Image();
i.Source = new BitmapImage(new Uri(dlg.FileName));
_Size = new Size(i.Source.Width, i.Source.Height);
}
}
Here is the code I use to write the text to the image.
public void WriteTextToImage(Point position)
{
SolidColorBrush brush = new SolidColorBrush((Color)cpColor.SelectedColor);
//Get something to write on (not an old receipt...)
TextBlock textBlock = new TextBlock();
//Say something useful... well something atleast...
textBlock.Text = tbCurrentLabel.Text;
textBlock.FontSize = slFontSize.Value;
textBlock.Foreground = brush;
Canvas.SetLeft(textBlock, position.X);
Canvas.SetTop(textBlock, position.Y);
canvas.Children.Add(textBlock);
//Need to update the canvas, was not seeing children before doing this.
canvas.UpdateLayout();
//Iterate the label text
IterateLabel();
}
Right now I am only getting a corner of the image that is currently visible in the canvas, and that is being cropped down, by fiddling with the DPI and _Size values I can change how much of the image is shown, but it is static to that one image (If I load a different image all the values are wrong and again I only get a small portion of the image)
If someone could please help me pull my head out of (ahem) I would be most grateful!
TIA

Save entire Canvas
RenderTargetBitmap bmp = new RenderTargetBitmap((int)Cnv.ActualWidth, (int)Cnv.ActualHeight, 100.0, 100.0, PixelFormats.Default);
bmp.Render(Cnv);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bmp));
System.IO.FileStream stream = System.IO.File.Create("G:\\Canvas.png");
encoder.Save(stream);
stream.Close();
Save only Background
BitmapFrame bmp = (BitmapFrame)(Cnv.Background as ImageBrush).ImageSource;
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(bmp);
System.IO.FileStream stream = System.IO.File.Create("G:\\Canvas.png");
encoder.Save(stream);
stream.Close();

Related

On changing the ImageSource of the Image using file picker, pop up raised that the file is in use

I am having an Image with default ImageSource, on picking up the new image using file picker it loads fine then on again the picking the previously used file, pop up raised that the file is still in use. When every time a new image is picked, it is working fine.
Is there any way to close or dispose the previously picked file or its ImageSource?
<Image x:Name="image" Source="Assets\RoadView.jpeg"></Image>
private void change_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "Image Files ( *.png, *.bmp *.jpg, *.gif, *.tif)|*.png;*.bmp;*.jpg;*.gif;*.tif";
openFileDialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
if (openFileDialog.ShowDialog() == true)
{
Stream stream = File.Open(openFileDialog.FileName, FileMode.Open);
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = stream;
bitmapImage.EndInit();
image.Source = bitmapImage;
}
}
You don't close the FileStream, hence the file is kept open and can't be opened a second time.
The easiest way to close the FileStream is to dispose of the object by means of a using block. In order to load the BitmapImage immediately before closing the stream, you also need to set BitmapCacheOption.OnLoad.
var bitmapImage = new BitmapImage();
using (var stream = File.Open(openFileDialog.FileName, FileMode.Open))
{
bitmapImage.BeginInit();
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.StreamSource = stream;
bitmapImage.EndInit();
}
image.Source = bitmapImage;

How to convert System.Windows.Media.DrawingImage into Stream?

I'am trying convert DrawingImage into MemoryStream. My code looks like this:
public MemoryStream ImageStream(DrawingImage drawingImage)
{
MemoryStream stream = new MemoryStream();
ImageSource imageSource = drawingImage;
if (imageSource != null)
{
BitmapSource bitmap = imageSource as BitmapSource;
if (bitmap != null)
{
BitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
encoder.Save(stream);
}
}
return stream;
}
But problem is after casting ImageSource into BitmapSource bitmap is always null. Any sugestion how to fix that?
The reason your bitmap variable is always null is because DrawingImage does not extend BitmapImage or vice-versa, so the cast is guaranteed to fail. A DrawingImage does not contain any pixel data of any kind. It references a Drawing that is used whenever the image needs to be rasterized.
How did you find yourself in a situation where you want to rasterize a DrawingImage and serialize it into a stream? I get the feeling you are going about something in an unusual way if you have need of a function like this.
Nevertheless, you could implement this function by drawing the DrawingImage to a DrawingVisual, rendering it to a RenderTargetBitmap, and then passing the render target to the encoder to serialize the raster data to a stream.
public MemoryStream ImageStream(DrawingImage drawingImage)
{
DrawingVisual visual = new DrawingVisual();
using (DrawingContext dc = visual.RenderOpen())
{
dc.DrawDrawing(drawingImage.Drawing);
dc.Close();
}
RenderTargetBitmap target = new RenderTargetBitmap((int)visual.Drawing.Bounds.Right, (int)visual.Drawing.Bounds.Bottom, 96.0, 96.0, PixelFormats.Pbgra32);
target.Render(visual);
MemoryStream stream = new MemoryStream();
BitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(target));
encoder.Save(stream);
return stream;
}
If you want something a little more generic, I would split this into two methods and change some of the types.
public BitmapSource Rasterize(Drawing drawing)
{
DrawingVisual visual = new DrawingVisual();
using (DrawingContext dc = visual.RenderOpen())
{
dc.DrawDrawing(drawing);
dc.Close();
}
RenderTargetBitmap target = new RenderTargetBitmap((int)drawing.Bounds.Right, (int)drawing.Bounds.Bottom, 96.0, 96.0, PixelFormats.Pbgra32);
target.Render(visual);
return target;
}
public void SavePng(BitmapSource source, Stream target)
{
BitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(source));
encoder.Save(target);
}
Then you could use it with any kind of stream. For example, to save the drawing to a file:
using (FileStream file = File.Create("somepath.png"))
{
SavePng(Rasterize(drawingImage.Drawing), file);
}

Silverlight: How to copy region from WriteableBitmap / BitmapImage?

I have such code for Bitmap wrapper.
I need to create overload constructor which cut some rectangle from source image and put it inside _wbmp.
Smth similar to public Bitmap(string fileName, Rectangle area).
Please share some solution.
public Bitmap(string fileName)
{
Uri uri = new Uri(fileName, UriKind.RelativeOrAbsolute);
StreamResourceInfo sri = null;
sri = Application.GetResourceStream(uri);
// Create a new WriteableBitmap object and set it to the JPEG stream.
BitmapImage bitmap = new BitmapImage();
bitmap.CreateOptions = BitmapCreateOptions.None;
bitmap.SetSource(sri.Stream);
_wbmp = new WriteableBitmap(bitmap);
}
Thank you
The WritableBitmap object has a Render method that you can use to render a new bitmap after adding some transformation. In your case you could create and new WritableBitmap with the correct new size to set the button-right corner and then add temporary image with your source and translate it to the left to set the upper left corner. Something like this:
public static WriteableBitmap CropBitmap(string fileName, int newTop, int newRight, int newBottom, int newLeft)
{
Uri uri = new Uri(fileName, UriKind.RelativeOrAbsolute);
StreamResourceInfo sri = null;
sri = Application.GetResourceStream(uri);
// Create a new WriteableBitmap object and set it to the JPEG stream.
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.CreateOptions = BitmapCreateOptions.None;
bitmapImage.SetSource(sri.Stream);
//calculate bounding box
int originalWidth = bitmapImage.PixelWidth;
int originalHeight = bitmapImage.PixelHeight;
int newSmallWidth = newRight - newLeft;
int newSmallHeight = newBottom - newTop;
//generate temporary control to render image
Image temporaryImage = new Image { Source = bitmapImage, Width = originalWidth, Height = originalHeight };
//create writeablebitmap
WriteableBitmap wb = new WriteableBitmap(newSmallWidth, newSmallHeight);
wb.Render(temporaryImage, new TranslateTransform { X = -newLeft, Y = -newTop });
wb.Invalidate();
return wb;
}

Save Controls.Image to PDF with more than 1 Control.Image

I'm here for a solution to a little unresolved problem. Maybe I haven't searched for the right things as I haven't found a solution and I hope you will be able to help.
So I have a WPF application where I load an image, here I have no problems. Next I have to add some points by clicking, so i have this function:
private void ClickMouse(object sender, MouseButtonEventArgs e)
{
Point p = e.GetPosition(this);
schema.PointFromScreen(p);
var myImage3 = new Image();
var bi3 = new BitmapImage();
bi3.BeginInit();
bi3.UriSource = new Uri(_clickImagePath, UriKind.Relative);
bi3.EndInit();
myImage3.Stretch = Stretch.Fill;
myImage3.Source = bi3;
myImage3.MinWidth = 30;
myImage3.MinHeight = 30;
myImage3.Width = 30;
myImage3.Height = 30;
myImage3.Name = _pointName;
var oMargin = new Thickness(p.X, p.Y, 0, 0);
myImage3.Margin = oMargin;
mainCanva.Children.Add(myImage3);
_listImg.Add(myImage3);
PointList.Items.Add(_pointName);
}
The problem is not here but I need a context.
Now on to the main problem. I have to save the final Image to make a PDF with. I use a piece of sample code I found here (http://stackoverflow.com/questions/1824989/how-to-store-system-windows-controls-image-to-local-disk) to convert to a jpg file using ITextsharp. The only thing I have on the PDF is the first picture, so I admitted that it probably take only the first image and not the other created images on it but I really don't know how to fix it. Does some fonction exist to make a kind of screenshot of the image zone? or maybe to pay attention to all image in the zone of the principal Control.Image ?
Thank you for your help.
You should use the following code to render your Canvas as an image :
var rect = new Rect(canvas.RenderSize);
var rtb = new RenderTargetBitmap((int)rect.Right, (int)rect.Bottom, 96d, 96d, System.Windows.Media.PixelFormats.Default);
rtb.Render(canvas);
//encode as PNG
varpngEncoder = new PngBitmapEncoder();
pngEncoder.Frames.Add(BitmapFrame.Create(rtb));
//save to memory stream
using(var ms = new System.IO.MemoryStream())
{
pngEncoder.Save(ms);
System.IO.File.WriteAllBytes("logo.png", ms.ToArray());
}
(Source : Write WPF output to image file )

Load image from file and print it using WPF... how?

I'm looking for an example of how to load an image from file and print it on a page using WPF. I'm having a hard time finding good information about WPF printing.
var bi = new BitmapImage();
bi.BeginInit();
bi.CacheOption = BitmapCacheOption.OnLoad;
bi.UriSource = new Uri("");
bi.EndInit();
var vis = new DrawingVisual();
using (var dc = vis.RenderOpen())
{
dc.DrawImage(bi, new Rect { Width = bi.Width, Height = bi.Height });
}
var pdialog = new PrintDialog();
if (pdialog.ShowDialog() == true)
{
pdialog.PrintVisual(vis, "My Image");
}
If you want more control then PrintDialog.PrintVisual gives you you have to wrap your image in a FixedDocumet.
You can find simple code that creates a fixed document here:
http://www.ericsink.com/wpf3d/B_Printing.html
did playing around with this.
Tamir's answer is a great answer, but the problem is, that it's using the original size of the image.
write a solution myself , whitch doesn't stretch the image if it's smaller than the pagesize and bring the image if it's to large at the page.
It can be used for multiply copies and can be used with both orientations.
PrintDialog dlg = new PrintDialog();
if (dlg.ShowDialog() == true)
{
BitmapImage bmi = new BitmapImage(new Uri(strPath));
Image img = new Image();
img.Source = bmi;
if (bmi.PixelWidth < dlg.PrintableAreaWidth ||
bmi.PixelHeight < dlg.PrintableAreaHeight)
{
img.Stretch = Stretch.None;
img.Width = bmi.PixelWidth;
img.Height = bmi.PixelHeight;
}
if (dlg.PrintTicket.PageBorderless == PageBorderless.Borderless)
{
img.Margin = new Thickness(0);
}
else
{
img.Margin = new Thickness(48);
}
img.VerticalAlignment = VerticalAlignment.Top;
img.HorizontalAlignment = HorizontalAlignment.Left;
for (int i = 0; i < dlg.PrintTicket.CopyCount; i++)
{
dlg.PrintVisual(img, "Print a Large Image");
}
}
It only works for pictures from files with a path at the moment, but with a little bit work you can adept it and passing only a BitmapImage.
And it is useable with borderless prints (if your printer does support it)
Had to go the way with BitmapImage because it loads the default size of the image.
Windows.Controls.Image doesn't shows the right height and width if you're loading the image directly there.
I'm knowing, that the question is very old, but it was very difficult to found some useful information during searching this.
Hopefully my post will help other people.
Just load the image and apply it to a visual. Then use the PrintDialog to do the work.
...
PrintDialog printer = new PrintDialog();
if (printer.ShowDialog()) {
printer.PrintVisual(myVisual, "A Page Title");
}

Resources