RenderTargetBitmap causes Insufficient Memory - c#

I have seen threads going around before with this problem, but I can't seem to find a solution to my issue. I have an application that allows the user to create their own canvases. Let's say that in this example, the user creates 150 canvases that are 745px x 1045px. Now there is a functionality that allows the user to export these all into separate PNG files. Here is my method that does that:
public static void ExportToPng(Uri path, Canvas surface)
{
if (path == null) return;
Size size = new Size(surface.Width, surface.Height);
surface.Measure(size);
surface.Arrange(new Rect(size));
surface.UpdateLayout();
RenderTargetBitmap renderBitmap =
new RenderTargetBitmap(
(int)size.Width,
(int)size.Height,
96d,
96d,
PixelFormats.Pbgra32);
renderBitmap.Render(surface);
using (FileStream outStream = new FileStream(path.LocalPath, FileMode.Create))
{
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
encoder.Save(outStream);
}
}
After about 45 images or so, the application breaks on the line: renderBitmap.Render(surface); saying:
Insufficient memory to continue the execution of the program.
Now I understand that this process can be a bit of a memory hog, but I feel like after an image is rendered and exported as a PNG, I should be able to dispose of it somehow before moving on to the next image. I'm just not sure how to do that. Any and all advice is appreciated!

Related

wpf - how to print a whole page to pdf with PDFSharp

So I want to know how to print my entire WPF page to a PDF file with PDFSharp.
I've already been looking at several articles but I can't seem to figure it out.
I want the pdf to look something like this:
I've already looked up on articles about drawing strings, lines name it. But creating every line, string and shape individually looks like a sloppy and bad idea to me.
Can anyone help me with this?
Articles will also be appreciated!
Thanks in advance
I Would say first export your control to image with RenderTargetBitmap and then use a library to export it to PDF.
Maybe this sample might help ?
http://www.techcognition.com/post/Create-PDF-File-From-WPF-Window-using-iTextsharp-1001
With this library
Here his the Control to Image class I'm using with sucess (I'm able to get a PNG snapshot of complex UI controls implying a very deep parent-child hierarchy)
The source is a WPF control container (usercontrol, grid, wahtever).
The path is the full path for PNG output file (C:\Temp\myImage.png)
public class ControlToImageSnapshot
{
/// <summary>
/// Conversion du controle en image PNG
/// </summary>
/// <param name="source">Contrôle à exporter</param>
/// <param name="path">Destination de l'export</param>
/// <param name="zoom">Taille désirée</param>
public static void SnapShotPng(FrameworkElement source, string path, double zoom = 1.0)
{
try
{
var dir = Path.GetDirectoryName(path);
if (dir != null && !Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
RenderTargetBitmap renderTarget = new RenderTargetBitmap((int)source.ActualWidth, (int)source.ActualHeight, 96, 96, PixelFormats.Pbgra32);
VisualBrush sourceBrush = new VisualBrush(source);
DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
using (drawingContext)
{
drawingContext.DrawRectangle(sourceBrush, null, new Rect(new Point(0, 0), new Point(source.ActualWidth, source.ActualHeight)));
}
renderTarget.Render(drawingVisual);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderTarget));
using (FileStream stream = new FileStream(path, FileMode.Create, FileAccess.Write))
{
encoder.Save(stream);
}
createPdfFromImage(path, #"C:\Temp\myfile.pdf");
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
public static void createPdfFromImage(string imageFile, string pdfFile)
{
using (var ms = new MemoryStream())
{
var document = new iTextSharp.text.Document(iTextSharp.text.PageSize.LETTER.Rotate(), 0, 0, 0, 0);
PdfWriter.GetInstance(document, new FileStream(pdfFile, FileMode.Create));
iTextSharp.text.pdf.PdfWriter.GetInstance(document, ms).SetFullCompression();
document.Open();
FileStream fs = new FileStream(imageFile, FileMode.Open);
var image = iTextSharp.text.Image.GetInstance(fs);
image.ScaleToFit(document.PageSize.Width, document.PageSize.Height);
document.Add(image);
document.Close();
//open pdf file
Process.Start("explorer.exe", pdfFile);
}
}
}
For pdfsharp its quite easy, you can pass in an array of bytes for the pdf image and pdf, ive used this function quite a lot when dealing with images in pdfsharp.
fairly self explanatory
Open pdf and the image into a memorystream
Get pdf setup and choose page to draw on
I always set interpolate to false, I get better results with the kind of images I'm dealing with, if you have shading in your image set it to true.
then all your left to do is draw the image on the pdf and return as a memorystream
public static byte[] AddImageToPdf(byte[] pdf, byte[] img, double x, double y)
{
using (var msPdf = new MemoryStream(pdf))
{
using (var msImg = new MemoryStream(img))
{
var image = Image.FromStream(msImg);
var document = PdfReader.Open(msPdf);
var page = document.Pages[0];
var gfx = XGraphics.FromPdfPage(page);
var ximg = XImage.FromGdiPlusImage(image);
ximg.Interpolate = false;
gfx.DrawImage(
ximg,
XUnit.FromCentimeter(x),
XUnit.FromCentimeter(y),
ximg.PixelWidth * 72 / ximg.HorizontalResolution,
ximg.PixelHeight * 72 / ximg.HorizontalResolution);
using (var msFinal = new MemoryStream())
{
document.Save(msFinal);
return msFinal.ToArray();
}
}
}
}
its hardcoded for page 1 in the pdf, easily extendable to pass in pages if you want, ill leave that as an exercise for yourself, at the end you get a nice byte array containing your pdf, no files need to touch the ground enroute if you import your image as a memorystream from your control and pass it in. another answer in this topic has a good way of getting the control image.

Image size is drastically increasing after applying a simple watermark

I've a set of images that I'm programmatically drawing a simple watermark on them using System.Windows and System.Windows.Media.Imaging (yes, not with GDI+) by following a tutorial in here.
Most of the images are not more than 500Kb, but after applying a simple watermark, which is a text with a transparent background, the image size is drastically increasing.
For example, a 440Kb image is becoming 8.33MB after applying the watermark with the below method, and that is shocking me.
private static BitmapFrame ApplyWatermark(BitmapFrame image, string waterMarkText) {
const int x = 5;
var y = image.Height - 20;
var targetVisual = new DrawingVisual();
var targetContext = targetVisual.RenderOpen();
var brush = (SolidColorBrush)(new BrushConverter().ConvertFrom("#FFFFFF"));
brush.Opacity = 0.5;
targetContext.DrawImage(image, new Rect(0, 0, image.Width, image.Height));
targetContext.DrawRectangle(brush, new Pen(), new Rect(0, y, image.Width, 20));
targetContext.DrawText(new FormattedText(waterMarkText, CultureInfo.CurrentCulture, FlowDirection.LeftToRight,
new Typeface("Batang"), 13, Brushes.Black), new Point(x, y));
targetContext.Close();
var target = new RenderTargetBitmap((int)image.Width, (int)image.Height, 96, 96, PixelFormats.Default);
target.Render(targetVisual);
var targetFrame = BitmapFrame.Create(target);
return targetFrame;
}
I've noticed that the image quality is improved compared than the original image. The image is more smoother and colors are more lighter. But, you know I don't really want this. I want the image to be as it is, but include the watermark. No quality increases, and of course no drastic changes in image size.
Is there any settings that I'm missing in here to tell my program to keep the quality as same as source image? How can I prevent the significant change of the image size after the changes in my ApplyWatermark method?
Edit
1. This is how I convert BitmapFrame to Stream. Then I use that Stream to save the image to AmazonS3
private Stream EncodeBitmap(BitmapFrame image) {
BitmapEncoder enc = new BmpBitmapEncoder();
enc.Frames.Add(BitmapFrame.Create(image));
var memoryStream = new MemoryStream();
enc.Save(memoryStream);
return memoryStream;
}
2. This is how I get the BitmapFrame from Stream
private static BitmapFrame ReadBitmapFrame(Stream stream) {
var photoDecoder = BitmapDecoder.Create(
stream,
BitmapCreateOptions.PreservePixelFormat,
BitmapCacheOption.None);
return photoDecoder.Frames[0];
}
3. This is how I read the file from local directory
public Stream FindFileInLocalImageDir() {
try {
var path = #"D:\Some\Path\Image.png";
return !File.Exists(path) ? null : File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read);
} catch (Exception) {
return null;
}
}
The problem is that when you edit the image, the compression is gone. A 730x1108 JPG with 433kB disc size with 32bit (you mentioned transparency, so ARGB) will need at least 730 * 1108 * 4 = 3,09MB on disc. Of course you can compress it afterwards again (for disc, network stream of what else).
This is the reason why image software always needs much memory even when working with compressed data.
Conclusion: You will need the free memory to work with the image. Not possible to have it otherwise completly at hand.
The reason I asked my question in the comments earlier, is because I noticed there were several different encoders available. A bitmap usually has a significantly larger file size, due to the amount of information it's storing about your image.
I haven't tested this myself, but have you tried a different encoder?
var pngEncoder = new PngBitmapEncoder();
pngEncoder.Frames.Add(ApplyWatermark(null, null));
MemoryStream stm = File.Create(image);
pngEncoder.Save(stm);
return stm;

Snapshot of canvas with small filesize - C# WPF

I have a puzzle game and I can create levels. When I save a level, it takes a snapshot of the canvas and then when I choose a level, it displays all the pictures of the levels next to their name as a thumbnail. However each image is around 1MB in size. I would like to get them to around 30KB in size. Also the file it makes cannot be edited by a photo editor to make it a smaller size even though it is a jpg. I see I have used a TiffBitmapEncoder whoops. Probably my issue with the photo editors.
Here is my code:
private void saveImage(object sender, EventArgs e)
{
string path = myImageNamePath;
FileStream fs = new FileStream(path, FileMode.Create);
RenderTargetBitmap bmp = new RenderTargetBitmap((int)myLevelDesigner.pbxMap.ActualWidth,
(int)myLevelDesigner.pbxMap.ActualHeight, 1 / 96, 1 / 96, PixelFormats.Pbgra32);
bmp.Render(myLevelDesigner.pbxMap);
BitmapEncoder encoder = new TiffBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bmp));
encoder.Save(fs);
fs.Close();
}
Oh my, is there a particular reason you are using the TIFF file format? I highly recommend using PNG instead, it is lossless and compressed.
Use PngBitmapEncoder instead, most graphics programs support PNG.
I would also recommend making it into an extension method that you can reuse throughout. Something like this:
public static class CanvasExtender
{
public static void SaveToImageFile(this Canvas canvas, string outputFile)
{
canvas.UpdateLayout();
var bitmap = new RenderTargetBitmap(canvas.ActualWidth, canvas.ActualHeight, 96d, 96d, PixelFormats.Pbgra32);
bitmap.Render(canvas);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
using(var outputStream = File.Create(outputFile))
encoder.Save(outputStream);
}
}

Load large image with memory efficiency

I am using .NET4.5, Windows Forms and C#.
I am loading an image onto a button using:
theButton.BackgroundImage = Image.FromFile("file.png");
The issue is that my button is 128x128 and the image is 4000x8000. The line above consumes very large amounts of memory because file.png is so large.
Does anyone know of a technique I can use to reduce this memory footprint? I am thinking of some function like this:
Image.FromFile(file,width,height);
Any pointers? Thanks.
Yes it works. It's quite simple to resize the image and then display it on button.
But, I don't think that the above code maintains the aspect ratio of the image.
It's quite simple to resize the image with aspect ratio; and then display it on button.
Below is the sample code helps you to resize the image by maintaining the aspect ratio.
You can define a new class or implement the "ResizeImage" method in an existing class. Whichever is comfortable to you.
public class ImageManipulation
{
public static Bitmap ResizeImage(Bitmap originalBitmap, int newWidth, int maxHeight, bool onlyResizeIfWider)
{
if (onlyResizeIfWider)
{
if (originalBitmap.Width <= newWidth)
{
newWidth = originalBitmap.Width;
}
}
int newHeight = originalBitmap.Height * newWidth / originalBitmap.Width;
if (newHeight > maxHeight)
{
// Resize with height instead
newWidth = originalBitmap.Width * maxHeight / originalBitmap.Height;
newHeight = maxHeight;
}
var alteredImage = new Bitmap(originalBitmap, new Size(newWidth, newHeight));
alteredImage.SetResolution(72, 72);
return alteredImage;
}
}
USAGE:
private void DisplayPhoto()
{
// make sure the file is JPEG or GIF
System.IO.FileInfo testFile = new System.IO.FileInfo(myFile);
// Create a new stream to load this photo into
FileStream myFileStream = new FileStream(myFile, FileMode.Open, FileAccess.Read);
// Create a buffer to hold the stream of bytes
photo = new byte[myFileStream.Length];
// Read the bytes from this stream and put it into the image buffer
myStream.Read(photo, 0, (int)myFileStream.Length);
// Close the stream
myFileStream.Close();
// Create a new MemoryStream and write all the information from
// the byte array into the stream
MemoryStream myStream = new MemoryStream(photo, true);
myStream.Write(photo, 0, photo.Length);
// Use the MemoryStream to create the new BitMap object
Bitmap FinalImage = new Bitmap(myStream);
upicPhoto.Image = ImageManipulation.ResizeImage(
FinalImage,
upicPhoto.Width,
upicPhoto.Height,
true);
// Close the stream
myStream.Close();
}
I think your best path here is to just resize the image, to 128x128.
An image that large is always going to take up a lot of memory, no matter what you do with it.
This will also allow you to make the image something that will look good at that size.
This is quite a general problem, AFAIK you have few possibilities
Compress image before uploading , in real world this will not work.
Put a check on size and dimensions of image, in real world it works, even linkedin, facebook they won't allow us to upload images above there specified dimensions.
Use buffering, this is cleanest way you can do in .net
Use some third party plugins or development enviornment, I have done it in Silverlight

How to convert every page in a XPS file to an image in C#?

Is there a way to convert every page in a XPS document to an image programmatically using C#?
I ran across this blog post from Josh Twist that appears to do what you want.
Cracking an XPS in WPF
On searching the net, there are many paid/trial programs that claim to do this (I have not tried any of them, so I can't vouch/list any of them). I assumed you want to write your own code.
Here is the 'meat' of the blog post (condensed):
Uri uri = new Uri(string.Format("memorystream://{0}", "file.xps"));
FixedDocumentSequence seq;
using (Package pack = Package.Open("file.xps", ...))
using (StorePackage(uri, pack)) // see method below
using (XpsDocument xps = new XpsDocument(pack, Normal, uri.ToString()))
{
seq = xps.GetFixedDocumentSequence();
}
DocumentPaginator paginator = seq.DocumentPaginator;
Visual visual = paginator.GetPage(0).Visual; // first page - loop for all
FrameworkElement fe = (FrameworkElement)visual;
RenderTargetBitmap bmp = new RenderTargetBitmap((int)fe.ActualWidth,
(int)fe.ActualHeight, 96d, 96d, PixelFormats.Default);
bmp.Render(fe);
PngBitmapEncoder png = new PngBitmapEncoder();
png.Frames.Add(BitmapFrame.Create(bmp));
using (Stream stream = File.Create("file.png"))
{
png.Save(stream);
}
public static IDisposable StorePackage(Uri uri, Package package)
{
PackageStore.AddPackage(uri, package);
return new Disposer(() => PackageStore.RemovePackage(uri));
}
Please refer to the accepted answer it is really helpful (it helped me also) .I just want to note to some very important point in that solution.
if you are implementing your own DocumentPaginator (as in my case) then that code will Not work because we will not get reference to your specific Paginator from the statement
DocumentPaginator paginator = seq.DocumentPaginator;
Casting this to your own paginator also will not work.
also that solution is very complex in case you have your own Paginator.
So I developed a simplified solution , which based on the accepted solution of this question and this worked exactly as needed.
// create your own paginator instead of this
// this is my specific own implementation for DocumentPaginator class
ReportPaginator paginator = new ReportPaginator(report);
Visual visual = paginator.GetPage(0).Visual; // first page - loop for all
RenderTargetBitmap bmp = new RenderTargetBitmap((int)paginator.PageSize.Width, (int)paginator.PageSize.Height, 96d, 96d, PixelFormats.Default);
bmp.Render(visual);
PngBitmapEncoder png = new PngBitmapEncoder();
png.Frames.Add(BitmapFrame.Create(bmp));
using (MemoryStream sm = new MemoryStream())
{
png.Save(sm);
return sm.ToArray();
}

Categories

Resources