The fastest way to convert canvas to the writeablebitmap in WPF? - c#

I currently have one writeablebitmap image and canvas with drawings and I want to send the images to the peer.In order to reduce the bandwidth, I would like to convert canvas to the writeablebitmap, thus I can blit both the images to a new writeablebitmap. The problem is I cannot find a good way to convert the canvas.
Therefore, I would like to ask if there is a direct way to convert the canvas to a writeablebitmap class.

This is taken from this blog post but instead of writing to a file, it writes to a WriteableBitmap.
public WriteableBitmap SaveAsWriteableBitmap(Canvas surface)
{
if (surface == null) return null;
// Save current canvas transform
Transform transform = surface.LayoutTransform;
// reset current transform (in case it is scaled or rotated)
surface.LayoutTransform = null;
// Get the size of canvas
Size size = new Size(surface.ActualWidth, surface.ActualHeight);
// Measure and arrange the surface
// VERY IMPORTANT
surface.Measure(size);
surface.Arrange(new Rect(size));
// Create a render bitmap and push the surface to it
RenderTargetBitmap renderBitmap = new RenderTargetBitmap(
(int)size.Width,
(int)size.Height,
96d,
96d,
PixelFormats.Pbgra32);
renderBitmap.Render(surface);
//Restore previously saved layout
surface.LayoutTransform = transform;
//create and return a new WriteableBitmap using the RenderTargetBitmap
return new WriteableBitmap(renderBitmap);
}

Related

Image is not drawn at the correct spot

Bitmap image = ReadBitmap("image.png");
Bitmap imageCopy = new Bitmap(image);
Bitmap canvas = new Bitmap(imageCopy.Width+100, imageCopy.Height);
// From this bitmap, the graphics can be obtained, because it has the right PixelFormat
using(Graphics g = Graphics.FromImage(canvas))
{
// Draw the original bitmap onto the graphics of the new bitmap
g.DrawImage(image, 0, 0);
}
// Use tempBitmap as you would have used originalBmp
InputPictureBox.Image = image;
OutputPictureBox.Image = canvas;
I haven't understood the output of this c# code.
The original image is not placed at the correct position. It should have been at (0, 0).
Also, I need a black background.
So, what is going on and how to correct this?
You are loading an Image, then a copy of this source is created using:
Bitmap bitmap = new Bitmap();
When you create a copy of an Image this way, you sacrifice/alter some details:
Dpi Resolution: if not otherwise specified, the resolution is set to the UI resolution. 96 Dpi, as a standard; it might be different with different screen resolutions and scaling. The System in use also affects this value (Windows 7 and Windows 10 will probably/possibly provide different values)
PixelFormat: If not directly copied from the Image source or explicitly specified, the PixelFormat is set to PixelFormat.Format32bppArgb.
From what you were saying, you probably wanted something like this:
var imageSource = Image.FromStream(new MemoryStream(File.ReadAllBytes(#"[SomeImageOfLena]"))), true, false)
var imageCopy = new Bitmap(imageSource.Width + 100, imageSource.Height, imageSource.PixelFormat))
imageCopy.SetResolution(imageSource.HorizontalResolution, imageSource.VerticalResolution);
using (var g = Graphics.FromImage(imageCopy)) {
g.Clear(Color.Black);
g.CompositingMode = CompositingMode.SourceCopy;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage(imageSource, (imageCopy.Width - imageSource.Width) / 2, 0);
pictureBox1.Image?.Dispose();
pictureBox2.Image?.Dispose();
pictureBox1.Image = imageSource;
pictureBox2.Image = imageCopy;
}
This is the result:
(The upper/lower frame black color is actually the Picturebox background color)
When the original Image Dpi Resolution is different from the base Dpi Resolution used when creating an Image copy with new Bitmap(), your results may be different from what is expected.
This is what happens with a source Image of 150, 96 and 72 Dpi in the same scenario:
Another important detail is the IDisposable nature of the Image object.
When you create one, you have to Dispose() of it; explicitly, calling the Dispose method, or implicitly, enclosing the Image contructor in a Using statement.
Also, possibly, don't assign an Image object directly loaded from a FileStream.
GDI+ will lock the file, and you will not be able to copy, move or delete it.
With the file, all resources tied to the Images will also be locked.
Make a copy with new Bitmap() (if you don't care of the above mentioned details), or with Image.Clone(), which will preserve the Image Dpi Resolution and PixelFormat.
I am not completely clear on what you are actually needing to do. But anyway, here is a WPF-friendly example of how to draw an image at a specific position inside another image.
Note if all you want to do is display the image in different size and/or put a black border around it, there are much simpler ways to do simply that, without having to create a second image, such as just laying out the image inside a panel that already has the border style you want.
Notice that I am using classes from the System.Windows.Media namespace because that is what WPF uses. These don't mix easily with the older classes from System.Drawing namespace (some of the class names conflict, and Microsoft's .Net framework lacks built-in methods for converting objects between those types), so normally one needs to simply decide whether to use one or the other sets of drawing tools. I assume you have been trying to use System.Drawing. Each has its own pros and cons that would take too long to explain here.
// using System.Windows.Media;
// using System.Windows.Media.Imaging;
private void DrawTwoImages()
{
// For InputPictureBox
var file = new Uri("C:\\image.png");
var inputImage = new BitmapImage(file);
// If your image is stored in a Resource Dictionary, instead use:
// var inputImage = (BitmapImage) Resources["image.png"];
InputPicture.Source = inputImage;
// imageCopy isn't actually needed for this example.
// But since you had it in yours, here is how it's done, anyway.
var imageCopy = inputImage.Clone();
// Parameters for setting up our output picture
int leftMargin = 50;
int topMargin = 5;
int rightMargin = 50;
int bottomMargin = 5;
int width = inputImage.PixelWidth + leftMargin + rightMargin;
int height = inputImage.PixelHeight + topMargin + bottomMargin;
var backgroundColor = Brushes.Black;
var borderColor = (Pen) null;
// Use a DrawingVisual and DrawingContext for drawing
DrawingVisual dv = new DrawingVisual();
using (DrawingContext dc = dv.RenderOpen())
{
// Draw the black background
dc.DrawRectangle(backgroundColor, borderColor, new Rect(0, 0, width, height));
// Copy input image onto output image at desired position
dc.DrawImage(inputImage, new Rect(leftMargin, topMargin,
inputImage.PixelWidth, inputImage.PixelHeight));
}
// For displaying output image
var rtb = new RenderTargetBitmap( width, height, 96, 96, PixelFormats.Pbgra32 );
rtb.Render(dv);
OutputPicture.Source = rtb;
}

Draw a bitmap in WPF

I am trying to draw some string to bitmap at certain position and copy a barcode bitmap to the new bitmap.I have not done with graphics before so i don't know where to start.
Can anyone guide me on this?my output of the bitmap is a receipt like.
Here is a solution. Please make a grid or canvas and put the barcode image and use a label with desired text and put the label on desired location relative to barcode grid. So, trick is you can immediately take screenshot of this grid using following code.Then, you are done.
public void ConvertToBitmapSource(UIElement element)
{
var target = new RenderTargetBitmap(
(int)element.RenderSize.Width, (int)element.RenderSize.Height,
96, 96, PixelFormats.Pbgra32);
target.Render(element);
var encoder = new PngBitmapEncoder();
var outputFrame = BitmapFrame.Create(target);
encoder.Frames.Add(outputFrame);
using (var file = File.OpenWrite("TestImage.png"))
{
encoder.Save(file);
}
}

How to overlap images and store them in new image objects

I have two images,now i want to overlay the images,such that the other image appears on center or left corner of the other image,and then when finally both the images are overlayed i can store it in another new image object,and i want to all this in code behind only not xaml,how to do this?
if (((Grid)sender).Children.Count > 0)
{
gridBackground = (ImageBrush)(((Grid)sender).Background);
gridBackImage = new System.Windows.Controls.Image();
gridBackImage.Source = gridBackground.ImageSource;
}
System.Windows.Controls.Image imgRejectIcon;
if (((Grid)sender).Children.Count > 0)
{
imgRejectIcon = (System.Windows.Controls.Image)(((Grid)sender).Children[0]);
}
Now i want to merge gridBackImage and imgRejection and store it in new image object
You can arrange your two Images in any way that you want and I'll leave that code for you to do (it'd much simpler to do in XAML). In WPF, all UI controls extend the Visual class. This is very useful for making BitmapImages from UI controls, when used with the RenderTargetBitmap.Render method.
First, arrange your two Image controls into a Grid, or other container control, and then you can pass the container control to the RenderTargetBitmap.Render method and create an Image something like this:
RenderTargetBitmap renderTargetBitmap =
new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
renderTargetBitmap.Render(yourContainerControl);
PngBitmapEncoder pngImage = new PngBitmapEncoder();
pngImage.Frames.Add(BitmapFrame.Create(renderTargetBitmap));
using (Stream fileStream = File.Create(filePath))
{
pngImage.Save(fileStream);
}

Replicating WinRT Pixel Density changes in WPF possibly using DPI

I am using WPF to generate tile images for my WinRT app. I have a tile UserControl which is converted to PNG and this works well (It does, please don't tell me otherwise). WinRT passes me a scale property 100% (320x150), 140% (434x210) and 180% (558x270) which I use to generate the correct size of image.
For when I want to use images in my tile. I have replicated the image selection functionality of WinRT (You can supply thee scales of images and WinRT apps automatically select the correct scale) in my tile UserControl's code behind. So depending on the scale I select a bigger or smaller image source. However, on the larger scales my font size remains the same size and looks really small. I don't think I need to change the font size based on the scale as this is not what happens in WinRT, so it must be the code I'm using to convert my UserControl to a PNG and something to do with DPI. Here is my conversion Code:
// I pass in 320x150 or 434x210 or 558x270 depending on the scale.
public static MemoryStream ToPng(
this FrameworkElement frameworkElement,
double width,
double height)
{
BitmapSource bitmapSource = ToBitmapSource(frameworkElement, width, height);
PngBitmapEncoder pngBitmapEncoder = new PngBitmapEncoder();
pngBitmapEncoder.Frames.Add(BitmapFrame.Create(bitmapSource));
MemoryStream memoryStream = new MemoryStream();
pngBitmapEncoder.Save(memoryStream);
memoryStream.Position = 0;
return memoryStream;
}
public static BitmapSource ToBitmapSource(
this FrameworkElement frameworkElement,
double width,
double height)
{
Size renderingSize = new Size(width, height);
frameworkElement.Measure(renderingSize);
Rect renderingRectangle = new Rect(new Point(0, 0), renderingSize);
frameworkElement.Arrange(renderingRectangle);
frameworkElement.UpdateLayout();
Rect bounds = VisualTreeHelper.GetDescendantBounds(frameworkElement);
RenderTargetBitmap renderBitmap = new RenderTargetBitmap(
(int)frameworkElement.ActualWidth,
(int)frameworkElement.ActualHeight,
96,
96,
PixelFormats.Pbgra32);
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
VisualBrush visualBrush = new VisualBrush(frameworkElement);
drawingContext.DrawRectangle(visualBrush, null, new Rect(new Point(), bounds.Size));
}
renderBitmap.Render(drawingVisual);
return renderBitmap;
}
Thanks for any help. Much appreciated.
I needed to change the above code so that I measure and arrange the frameworkElement as 310x150. I then render it to the final scale size e.g. 558x270 and set the DPI to (96/100)*scale where the scale is 180 in this case.

How to merge layers and save as an image in WPF?

I have two layers. The first layer is Image control. The source of it is Bitmap Image. And this is the background layer. The second one, which is the front layer is a canvas on which I can draw geometry objects (such as line, polyline, rectangle etc.) and the background of canvas is transparent.
How can I merge these two layers and save it as an image using WPF.
What do you mean by "Layers"? Just two controls sat in the same cell of a grid? If you have both "layers" sat in another container (such as a grid, or even the window) then you can use RenderTargetBitmap with that container to get your image. I have some details, an extension method for taking WPF "Screenshots" on my blog.
You can get bitmap of your parent panel on which you have placed your image control and canvas.
How is the code to get the bitmap of UIElement in WPF.
RenderTargetBitmap bmp = new RenderTargetBitmap(Width, Height, 96, 96, PixelFormats.Pbgra32);
bmp.Render(parentPanel);
Use something like, call this method from your canvas (this is canvas) -
private Bitmap ImageGenerator()
{
var transform = this.LayoutTransform;
// Call UpdateLayout to make sure changes all changes
// while drawing objects on canvas are reflected
var layer = AdornerLayer.GetAdornerLayer(this);
layer?.UpdateLayout();
// Get the size of canvas
var size = new System.Windows.Size(this.ActualWidth, this.ActualHeight);
// Measure and arrange the surface
// VERY IMPORTANT
this.Measure(size);
this.Arrange(new Rect(RenderSize));
RenderTargetBitmap renderBitmap =
new RenderTargetBitmap(
(int)this.ActualWidth,
(int)this.ActualHeight,
96d,
96d,
PixelFormats.Pbgra32);
renderBitmap.Render(this);
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
// push the rendered bitmap to it
encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
var stream = new MemoryStream();
encoder.Save(stream);
this.LayoutTransform = transform;
return new Bitmap(stream);
}

Categories