Snapshot of canvas with small filesize - C# WPF - c#

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

Related

RenderTargetBitmap causes Insufficient Memory

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!

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;

System.Drawing.Image.RotateFlip(RotateFlipType)

I have the following code to rotate an image in C#:
using (var stream = new FileStream(path, FileMode.Open))
{
using (var image = Image.FromStream(stream))
{
stream.Close();
image.RotateFlip(rotateType);
image.Save(path1, ImageFormat.Png);
image.Dispose();
}
}
If the original file size was 700 KiB, the new rotated file has size of 7+ MiB.
What is wrong with this code? Any help is much appreciated.
Update:
I tried changing the line image.Save(path1, ImageFormat.Png) to image.Save(path1) and image.Save(path1, image.RawFormt) with no improvement.
C# - How to change PNG quality or color depth
This guy's question looks similar to the same thing you are seeing.
PNG is a bitmap file format:
higher filesize compared to jpg
Because of this you should safe your image as jpg:
Thus lossless PNG format is best suited for pictures still under edition - and the lossy formats, like JPEG, are best for the final distribution of photographic images, because in this case JPG files are usually smaller [...]
Source: wikipedia
Try safing the image in JPEG via:
image.Save(path, YourClass.GetImageFormat(image));
Tests:
Rotating an JPG file with this method and the size stays the same.
Rotating a 15.7MiB BMP file, the new size is ~800kiB.
To use the existing file format, use this extension method:
public static System.Drawing.Imaging.ImageFormat GetImageFormat(System.Drawing.Image img)
{
if (img.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Jpeg))
return System.Drawing.Imaging.ImageFormat.Jpeg;
if (img.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Bmp))
return System.Drawing.Imaging.ImageFormat.Bmp;
if (img.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Png))
return System.Drawing.Imaging.ImageFormat.Png;
if (img.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Emf))
return System.Drawing.Imaging.ImageFormat.Emf;
if (img.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Exif))
return System.Drawing.Imaging.ImageFormat.Exif;
if (img.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Gif))
return System.Drawing.Imaging.ImageFormat.Gif;
if (img.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Icon))
return System.Drawing.Imaging.ImageFormat.Icon;
if (img.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.MemoryBmp))
return System.Drawing.Imaging.ImageFormat.MemoryBmp;
if (img.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Tiff))
return System.Drawing.Imaging.ImageFormat.Tiff;
else
return System.Drawing.Imaging.ImageFormat.Wmf;
}
Source: StackOverflow
Remember you have to look for the format before you manipulate the image.
Otherwise the image will be recognised as MemoryBmp.
using (var stream = new FileStream(path, FileMode.Open))
{
using (var image = Image.FromStream(stream))
{
stream.Close();
var format = YourClass.GetImageFormat(image);
image.RotateFlip(RotateFlipType.Rotate180FlipNone);
image.Save(path, format);
image.Dispose();
}
}

Save wpf view as image, preferably .png

I have searched and understand how to save an image in WPF by using BmpBitmapEncoder. My program has a MVVM view that I want to save as an image. Is it possible to set it as BitmapFrame so I can encode it? If so, is there an online tutorial?
Listed below is the view I want to save.
<Grid>
<view:OverallView Grid.Row="1"
Visibility="{Binding IsOverallVisible,Converter={StaticResource B2VConv}}" />
</Grid>
OverallView is a user control.
If setting a view as a BitmapFrame is not possible, what wpf elements can be set as a BitmapSource/Frame?
You can return it as RenderTargetBitmap:
public static RenderTargetBitmap GetImage(OverallView view)
{
Size size = new Size(view.ActualWidth, view.ActualHeight);
if (size.IsEmpty)
return null;
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(view), null, new Rect(new Point(), size));
context.Close();
}
result.Render(drawingvisual);
return result;
}
After that you can use the PngBitmapEncoder to save it as PNG and save it to stream, e.g.:
public static void SaveAsPng(RenderTargetBitmap src, Stream outputStream)
{
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(src));
encoder.Save(outputStream);
}
FIX: bitmap => result
Kudos to the accepted answer above for leading me down the path to a solution, but that's actually over-complicating the requirements. Specifically, you don't need to create a DrawingVisual to pass to the Render call. You can pass the view directly since it's already a Visual, which the Render call accepts.
Here's a simplified version that I've also changed into an extension method on FrameworkElement so you can use it easily with any control.
Note: Although Render accepts a Visual, you can't extend Visual since you need the actual width and height to create the RenderTargetBitmap, but chances are for that same reason you're probably already using a FrameworkElement anyway.
public static RenderTargetBitmap? CopyAsBitmap(this FrameworkElement frameworkElement) {
var targetWidth = (int)frameworkElement.ActualWidth;
var targetHeight = (int)frameworkElement.ActualHeight;
// Exit if there's no 'area' to render
if (targetWidth == 0 || targetHeight == 0)
return null;
// Prepare the rendering target
var result = new RenderTargetBitmap(targetWidth, targetHeight, 96, 96, PixelFormats.Pbgra32);
// Render the framework element into the target
result.Render(frameworkElement);
return result;
}
I then have a second extension method that takes any BitmapSource (which RenderTargetBitmap is a subclass of) and returns the bytes based on the provided encoder.
public static byte[] Encode(this BitmapSource bitmapSource, BitmapEncoder bitmapEncoder){
// Create a 'frame' for the BitmapSource, then add it to the encoder
var bitmapFrame = BitmapFrame.Create(bitmapSource);
bitmapEncoder.Frames.Add(bitmapFrame);
// Prepare a memory stream to receive the encoded data, then 'save' into it
var memoryStream = new MemoryStream();
bitmapEncoder.Save(memoryStream);
// Return the results of the stream as a byte array
return memoryStream.ToArray();
}
And here's how you use it all together. This code renders any FrameworkElement as a PNG:
var renderTargetBitmap = someFrameworkElement.CopyAsBitmap();
var pngData = renderTargetBitmap.Encode(new PngBitmapEncoder());
File.WriteAllBytes(#"C:\Users\SomeUser\Desktop\TestOutput.png", pngData);
Or more succinctly...
var pngData = someFrameworkElement.CopyAsBitmap().Encode(new PngBitmapEncoder());
File.WriteAllBytes(#"C:\Users\SomeUser\Desktop\TestOutput.png", pngData);
To instead render it as a JPG, just change the encoder, like so...
var jpegData = someFrameworkElement.CopyAsBitmap().Encode(new JpegBitmapEncoder());
File.WriteAllBytes(#"C:\Users\SomeUser\Desktop\TestOutput.jpg", jpegData);
Additionally, since RenderTargetBitmap is ultimately an ImageSource (via BitmapSource), you can also set it as the Source property of an Image control directly, like so...
someImage.Source = someFrameworkElement.CopyAsBitmap();

Categories

Resources