Help saving a visual to file with PixelShader effect - WPF - wpf

I'm trying to save a wpf control to file, but I'm applying a PixelShader effect to it, and when I try to save, the saved image is entirely white, black or red... deppends on the parameters of the effect.
I'm using the code here: WPF - Programmatic Binding on a BitmapEffect
how can I properly save it?
thanks!
UPDATE:
the code I'm using is:
BitmapSource bitmap = preview.Source as BitmapImage;
Rectangle r = new Rectangle();
r.Fill = new ImageBrush(bitmap);
r.Effect = effect;
Size sz = new Size(bitmap.PixelWidth, bitmap.PixelHeight);
r.Measure(sz);
r.Arrange(new Rect(sz));
var rtb = new RenderTargetBitmap(bitmap.PixelWidth, bitmap.PixelHeight, bitmap.DpiX, bitmap.DpiY, PixelFormats.Pbgra32);
rtb.Render(r);
PngBitmapEncoder png = new PngBitmapEncoder();
png.Frames.Add(BitmapFrame.Create(rtb));
Stream stm = File.Create("new.png");
png.Save(stm);
stm.Close();

Try this piece of code:
/// <summary>
/// Creates a screenshot of the given visual at the desired dots/inch (DPI).
/// </summary>
/// <param name="target">Visual component of which to capture as a screenshot.</param>
/// <param name="dpiX">Resolution, in dots per inch, in the X axis. Typical value is 96.0</param>
/// <param name="dpiY">Resolution, in dots per inch, in the Y axis. Typical value is 96.0</param>
/// <returns>A BitmapSource of the given Visual at the requested DPI, or null if there was an error.</returns>
public static BitmapSource CaptureScreen(Visual target, double dpiX, double dpiY)
{
if (target == null)
{
return null;
}
RenderTargetBitmap rtb = null;
try
{
// Get the actual size
Rect bounds = VisualTreeHelper.GetDescendantBounds(target);
rtb = new RenderTargetBitmap((int)(bounds.Width * dpiX / 96.0),
(int)(bounds.Height * dpiY / 96.0),
dpiX,
dpiY,
PixelFormats.Pbgra32);
DrawingVisual dv = new DrawingVisual();
using (DrawingContext ctx = dv.RenderOpen())
{
VisualBrush vb = new VisualBrush(target);
ctx.DrawRectangle(vb, null, new Rect(new Point(), bounds.Size));
}
rtb.Render(dv);
}
catch (Exception ex)
{
Console.Error.WriteLine("Error capturing image: " + ex.Message);
return null;
}
return rtb;
}

Related

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);
}

How can I capture an entire image from a canvas?

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();

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;
}

WPF Memory Leak using RenderTargetBitmap?

I'm a little baffled by a memory leak in my WPF code. I'm rendering some 3D geometry to several RenderTargetBitmaps, then rendering each of those to a large, master RenderTargetBitmap. But when I do this, I get a memory leak that crashes my app after just a minute or two.
I've reproduced the error in the following simplified piece of code.
private void timer1_Tick(object sender, EventArgs e) {
// if first time, create final stitch bitmap and set UI image source
if (stitch == null) {
stitch = new RenderTargetBitmap(1280, 480, 96, 96, PixelFormats.Pbgra32);
myImage.Source = stitch;
}
// create visual and render to img1
Rect rect = new Rect(new Point(160, 100), new Size(320, 80));
DrawingVisual dvis = new DrawingVisual();
using (DrawingContext dc = dvis.RenderOpen()) {
dc.DrawRectangle(System.Windows.Media.Brushes.LightBlue, (System.Windows.Media.Pen)null, rect);
}
RenderTargetBitmap img1 = new RenderTargetBitmap(640, 480, 96, 96, PixelFormats.Pbgra32);
img1.Render(dvis);
// create visual and render to final stitch
DrawingVisual vis = new DrawingVisual();
using (DrawingContext dc = vis.RenderOpen()) {
dc.DrawImage(img1, new Rect(0, 0, 640, 480));
}
stitch.Clear();
stitch.Render(vis);
}
Can anyone see anything obvious that is going wrong here? Why would this code have an egregious memory leak?
if you monitor behaviors of the RenderTargetBitmap class using Resource Monitor, you can see each time this class called, you lose 500KB of your memory. my Answer to your Question is: Dont use RenderTargetBitmap class so many times
You cant even release the Used Memory of RenderTargetBitmap.
If you really need using RenderTargetBitmap class, just add these lines at End of your code.
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
This maybe solve your problem:
private void timer1_Tick(object sender, EventArgs e) {
// if first time, create final stitch bitmap and set UI image source
if (stitch == null) {
stitch = new RenderTargetBitmap(1280, 480, 96, 96, PixelFormats.Pbgra32);
myImage.Source = stitch;
}
// create visual and render to img1
Rect rect = new Rect(new Point(160, 100), new Size(320, 80));
DrawingVisual dvis = new DrawingVisual();
using (DrawingContext dc = dvis.RenderOpen()) {
dc.DrawRectangle(System.Windows.Media.Brushes.LightBlue, (System.Windows.Media.Pen)null, rect);
}
RenderTargetBitmap img1 = new RenderTargetBitmap(640, 480, 96, 96, PixelFormats.Pbgra32);
img1.Render(dvis);
// create visual and render to final stitch
DrawingVisual vis = new DrawingVisual();
using (DrawingContext dc = vis.RenderOpen()) {
dc.DrawImage(img1, new Rect(0, 0, 640, 480));
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
stitch.Clear();
stitch.Render(vis);
}

How do I convert from a Brush (e.g. DrawingBrush) to a BitmapSource?

I have a DrawingBrush with some vector graphics. I want to convert it to BitmapSource as an intermediate step to getting it to Bitmap. What's the (best) way to do this?
public static BitmapSource BitmapSourceFromBrush(Brush drawingBrush, int size = 32, int dpi = 96)
{
// RenderTargetBitmap = builds a bitmap rendering of a visual
var pixelFormat = PixelFormats.Pbgra32;
RenderTargetBitmap rtb = new RenderTargetBitmap(size, size, dpi, dpi, pixelFormat);
// Drawing visual allows us to compose graphic drawing parts into a visual to render
var drawingVisual = new DrawingVisual();
using (DrawingContext context = drawingVisual.RenderOpen())
{
// Declaring drawing a rectangle using the input brush to fill up the visual
context.DrawRectangle(drawingBrush, null, new Rect(0, 0, size, size));
}
// Actually rendering the bitmap
rtb.Render(drawingVisual);
return rtb;
}

Resources