Image gets mangled when saving InkCanvas to byte array to file - c#

I have a WPF application using InkCanvas. When I render the bitmap, save to a memory stream, write the resulting bytes to a file, and then open that file in paint, the image is mangled. Any idea what I may be doing wrong here? Tried several solutions found here on SO and also on codeproject. It's pretty clear that it's capturing part of the InkCanvas but the majority of it is black (I assume null bytes).
EDIT: also tried with/without margin. Here are the other links I've tried:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/ef71237c-5dfb-4d6c-a402-e8cb02b74e99/how-to-convert-inkcanvas-strokes-to-a-bitmap-or-byte-array?forum=wpf
Converting InkCanvas Strokes to a Byte Array and back again
InkCanvas Load/Save operations
http://www.centrolutions.com/Blog/post/2008/12/09/Convert-WPF-InkCanvas-to-Bitmap.aspx
https://social.msdn.microsoft.com/Forums/vstudio/en-US/ba4dc89f-0169-43a9-8374-68e1fb34a222/saving-inkcanvas-as-image?forum=wpf
I need the resultant file to be a bitmap/PNG so it can be viewed on another machine.
private byte[] ConvertInkCanvasToByteArray()
{
int margin = (int)this.icSignature.Margin.Left;
int width = (int)this.icSignature.ActualWidth - margin;
int height = (int)this.icSignature.ActualHeight - margin;
RenderTargetBitmap rtb = new RenderTargetBitmap(width, height, 96d, 96d, PixelFormats.Default);
rtb.Render(icSignature);
BmpBitmapEncoder encoder = new BmpBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(rtb));
byte[] bitmapBytes;
using (MemoryStream ms = new MemoryStream())
{
encoder.Save(ms);
ms.Position = 0;
bitmapBytes = ms.ToArray();
}
return bitmapBytes;
}
From the InkCanvas:
And then mangled:

To avoid any problem with the InkCanvas' Margin, you could draw it into an intermediate DrawingVisual:
private byte[] ConvertInkCanvasToByteArray()
{
var rect = new Rect(icSignature.RenderSize);
var visual = new DrawingVisual();
using (var dc = visual.RenderOpen())
{
dc.DrawRectangle(new VisualBrush(icSignature), null, rect);
}
var rtb = new RenderTargetBitmap(
(int)rect.Width, (int)rect.Height, 96d, 96d, PixelFormats.Default);
rtb.Render(visual);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(rtb));
using (var stream = new MemoryStream())
{
encoder.Save(stream);
return stream.ToArray();
}
}

Related

Copy image of Xceed DataGridControl to bitmap

I'm using Xceed WPF Toolkit (Community Edition) DataGridControl, and I would like to create a bitmap from the control (either to put on the clipboard or save to a png).
I have tried using a RenderBitmapTarget, but it will only copy the control as it is rendered on the screen (my grid is bigger than the screen).
My RenderBitmapTarget code looks like this:
RenderTargetBitmap rtb = new RenderTargetBitmap((int)control.ActualWidth, (int)control.ActualHeight, 96, 96, PixelFormats.Pbgra32);
rtb.Render(control);
PngBitmapEncoder png = new PngBitmapEncoder();
png.Frames.Add(BitmapFrame.Create(rtb));
MemoryStream stream = new MemoryStream();
png.Save(stream);
Image image = Image.FromStream(stream);
I've tried specifying a larger size (both in the RenderTargetBitmap constructor, and specifying new width/height for the control, but both just yielded the same image on a larger canvas.
Any thoughts?
Ok, I figured it out....
The final key was to disable delayed loading on the DataGridControl.View...here is my final code:
XAML:
<xcdg:DataGridControl x:Name="CEGrid">
<xcdg:DataGridControl.View>
<xcdg:TableflowView IsDeferredLoadingEnabled="False"/>
</xcdg:DataGridControl.View>
</xcdg:DataGridControl>
C# code-behind:
double tempWidth = CEGrid.ActualWidth;
double tempHeight = CEGrid.ActualHeight;
CEGrid.Width = double.NaN;
CEGrid.Height = double.NaN;
CEGrid.UpdateLayout();
RenderTargetBitmap rtb = new RenderTargetBitmap((int)CEGrid.ActualWidth, (int)CEGrid.ActualHeight, 96, 96, PixelFormats.Pbgra32);
rtb.Render(CEGrid);
PngBitmapEncoder pbe = new PngBitmapEncoder();
pbe.Frames.Add(BitmapFrame.Create(rtb));
MemoryStream stream = new MemoryStream();
pbe.Save(stream);
System.Drawing.Bitmap image = (System.Drawing.Bitmap)System.Drawing.Image.FromStream(stream);
CEGrid.Width = tempWidth;
CEGrid.Height = tempHeight;

Using C#, load .png images with transparency via URI, crop them and draw them on a canvas, then save the canvas images as a png file

My goal is to load .png images with transparency via URI, crop them and draw them on a canvas, then save the canvas images as a png file.
In javascript, it would look like:
var canvas = document.createElement("canvas");
var ctx = canvas.getContext('2d');
var img = new Image();
img.src = "a.png";
ctx.drawImage(img,10,10,20,20,30,30,10,10);
//drawing more images...
something(canvas.toDataURL('image/png'));
How could I do that in C# Visual Studios 2013? What would come the closest to this JS code?
I don't mind using WPF or Winforms. I do not need to be able to display the image. I only need to be able to save it.
One way to do it is with GDI+ (assumes using System.Drawing;):
using (var b = new Bitmap()) { // This is your canvas
Graphics g = Graphics.FromImage(b); // This is your graphics context
g.DrawImage(Image.FromFile("a.png"),
new Rectangle(30, 30, 10, 10), 10, 10, 20, 20,
GraphicsUnit.Pixel);
// Do something with b (e.g. b.Save(…))
}
If you want the same data URI, it’s (assumes using System.Drawing.Imaging;):
using (var ms = new MemoryStream()) {
using (var b = new Bitmap()) {
// …
b.Save(ms, ImageFormat.Png);
}
string base64 = Convert.ToBase64String(ms.ToArray());
something("data:image/png;base64," + base64);
}
You can use WPF as an image generator.
The namespaces you'll need include:
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
And a sample snippet of code to get you started:
DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
// Let's draw a rectangle!
var gradientBrush = new LinearGradientBrush();
gradientBrush.GradientStops.Add(new GradientStop(Colors.Azure, 0.0));
gradientBrush.GradientStops.Add(new GradientStop(Colors.SteelBlue, 1.0));
drawingContext.DrawRectangle(gradientBrush, null, new Rect(0, 0, _imageWidth, _imageHeight));
drawingContext.Close();
// Now to save it
RenderTargetBitmap bmp = new RenderTargetBitmap(_imageWidth, _imageHeight, 96, 96, PixelFormats.Pbgra32);
bmp.Render(drawingVisual);
PngBitmapEncoder png = new PngBitmapEncoder();
png.Frames.Add(BitmapFrame.Create(bmp));
byte[] imageBinary = null;
using (System.IO.MemoryStream memoryStream = new System.IO.MemoryStream())
{
png.Save(memoryStream);
imageBinary = memoryStream.GetBuffer();
}

Get all pictures and save it to file

I have a UniformGrid containing all my video thumbnails taken (they are all System.Windows.Control.Image). My goal here is to save a jpg of all the thumbnails after I click a button. Is there a way to grab a bitmap image or something from a UbiformGrid?
I am using C# with WPF.
Edit: like a screenshot. But I don't want to window border, only the grid content.
Edit2: I finally found a solution. Thanks for the help.
RenderTargetBitmap renderTarget = new RenderTargetBitmap((int)ThumbnailPanel.Width,
(int)ThumbnailPanel.Height, 96, 96, PixelFormats.Pbgra32);
VisualBrush sourceBrush = new VisualBrush(ThumbnailPanel);
DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
using (drawingContext)
{
drawingContext.DrawRectangle(sourceBrush, null, new Rect(new Point(0, 0), new Point(ThumbnailPanel.Width, ThumbnailPanel.Height)));
}
renderTarget.Render(drawingVisual);
JpegBitmapEncoder jpgEncoder = new JpegBitmapEncoder();
jpgEncoder.QualityLevel = 80;
jpgEncoder.Frames.Add(BitmapFrame.Create(renderTarget));
Byte[] _imageArray;
using (MemoryStream outputStream = new MemoryStream())
{
jpgEncoder.Save(outputStream);
_imageArray = outputStream.ToArray();
}
FileStream fileStream = new FileStream(#"myThumbnails.jpg", FileMode.Create, FileAccess.ReadWrite);
BinaryWriter binaryWriter = new BinaryWriter(fileStream);
binaryWriter.Write(_imageArray);
binaryWriter.Close();
Sure, just iterate through your collection of images with for or foreach and use the Image.GetThumbnailImage method from the System.Drawing namespace to create thumbnails... it's that simple.
For example:
foreach (var img in myImages)
{
var thumb = image.GetThumbnailImage(thumbnailSize.Width, thumbnailSize.Height, null, IntPtr.Zero);
//Do something with the thumbnail
thumb.Save(output)
}

Set background color in Bitmap Image

I want to save my canvas to image. It works but background color is black. How I must add to change the color?
I use this code:
Size size = new Size(surface.Width, surface.Height);
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);
// Create a file stream for saving image
using (FileStream outStream = new FileStream(filename, FileMode.Create))
{
BmpBitmapEncoder encoder = new BmpBitmapEncoder();
// push the rendered bitmap to it
encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
// save the data to the stream
encoder.Save(outStream);
}
Try this
Size size = new Size(surface.Width, surface.Height);
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);
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
VisualBrush visualBrush = new VisualBrush(surface);
drawingContext.DrawRectangle(visualBrush, null,
new Rect(new Point(), new Size(size.Width, size.Height)));
}
renderBitmap.Render(drawingVisual);
// Create a file stream for saving image
using (FileStream outStream = new FileStream(filename, FileMode.Create))
{
BmpBitmapEncoder encoder = new BmpBitmapEncoder();
// push the rendered bitmap to it
encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
// save the data to the stream
encoder.Save(outStream);
}
Try PixelFormats.Default or PixelFormats.Bgra32 or PixelFormats.Rgb24 instead of PixelFormats.Pbgra32.
The P stands for pre-multiplied - the assumption is that each channel is pre-multiplied by alpha.
MSDN reference

Rendering an image at runtime in WPF

I have posted several questions related to this problem I am having and I am starting to believe this cannot be done. Here is the back story.
I have an ASP.NET application from which I want to generate a .png image. This .png image needs to be constructed from either XAML or a WPF Visual Tree. Because of this, I must generate the .png image in an STA thread. Everything works fine until my XAML/WPF Visual Tree includes an Image (as in a System.Windows.Controls.Image). My .png file gets generated correctly except the Image element does not show the referenced picture. The referenced picture is located at a remote URL. No errors or exceptions are thrown.
How do I create a .png image from some XAML/WPF Visual Tree that includes a System.Windows.Controls.Image element? The resulting .png must include the picture referenced in the Image element. I have tried the following code in a variety of ways:
string address = "http://imgtops.sourceforge.net/bakeoff/o-png24.png";
WebClient webClient = new WebClient();
byte[] imageContent = webClient.DownloadData(address);
Image image = new Image();
using (MemoryStream memoryStream = new MemoryStream(imageContent))
{
BitmapImage imageSource = new BitmapImage();
imageSource.BeginInit();
imageSource.StreamSource = memoryStream;
imageSource.EndInit();
image.Source = imageSource;
}
// Set the size
image.Height = 200;
image.Width = 300;
// Position the Image within a Canvas
image.SetValue(Canvas.TopProperty, 1.0);
image.SetValue(Canvas.LeftProperty, 1.0);
Canvas canvas = new Canvas();
canvas.Height = 200;
canvas.Width = 300;
canvas.Background = new SolidColorBrush(Colors.Purple);
canvas.Children.Add(image);
// Create the area
Size availableSize = new Size(300, 200);
frameworkElement.Measure(availableSize);
frameworkElement.Arrange(new Rect(availableSize));
// Convert the WPF representation to a PNG file
BitmapSource bitmap = RenderToBitmap(frameworkElement);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
// Generate the .png
FileStream fileStream = new FileStream(filename, FileMode.Create);
encoder.Save(fileStream);
public BitmapSource RenderToBitmap(FrameworkElement target)
{
int actualWidth = 300;
int actualHeight = 200;
Rect boundary = VisualTreeHelper.GetDescendantBounds(target);
RenderTargetBitmap renderBitmap = new RenderTargetBitmap(actualWidth, actualHeight, 96, 96, PixelFormats.Pbgra32);
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext context = drawingVisual.RenderOpen())
{
VisualBrush visualBrush = new VisualBrush(target);
context.DrawRectangle(visualBrush, null, new Rect(new Point(), boundary.Size));
}
renderBitmap.Render(drawingVisual);
return renderBitmap;
}
Thank you for your help.
You are rendering the output bitmap correctly, it is just the input bitmap you are screwwing up :).
BitmapImage requires access to the StreamSource property until it fires the DownloadCompleted event, but the 'using' block Dispose()s of the MemoryStream before it has a chance! You could simply unwrap the MemoryStream from the using block and let the GC handle it (if you do, I would recommend setting the BitmapImage.CacheOption to BitmapCacheOption.None, so it uses the stream directly, rather than a copy), but I would use the UriSource property and wait for the DownloadComplete event before rendering.

Categories

Resources