I have a situation where I have a canvas with an image and I lay multiple canvas controls over it and each canvas may have multiple controls drawn on them (Ellipses, Lines, etc). What is the best/most efficient way to save all these canvas controls toone image? I assume WritableBitmap, but how will this work with multiple canvas controls? One given is that each canvas will be the same size.
EDIT:
This is what I tried. I also tried with the dpi of the original image, which gives me the tiny result.
BitmapSource bms = (BitmapSource)this.ctlImage.Source;
Matrix m = PresentationSource.FromVisual(this.ctlImgCanvas).CompositionTarget.TransformToDevice;
RenderTargetBitmap rtb = new RenderTargetBitmap(bms.PixelWidth, bms.PixelHeight, m.M11 * 96.0, m.M22 * 96.0, PixelFormats.Pbgra32);
rtb.Render(this.ctlImgCanvas);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(rtb));
encoder.Save(memStream);
This will take care of everything including children of the canvas. You don't need to know the size of the canvas in advance.
Matrix m =
PresentationSource.FromVisual(canvas).CompositionTarget.TransformToDevice;
BitmapSource panelBitmap = new RenderTargetBitmap(
(int)Math.Floor(canvas.ActualWidth),
(int)Math.Floor(canvas.ActualHeight),
m.M11 * 96.0, // For DPI. This hardcoded value is correct. Check WPF DPI
m.M22 * 96.0,
PixelFormats.Default);
panelBitmap.Render(canvas);
using (FileStream fs = File.Open([filename], FileModel.Create, FileAccess.Write))
{
BitmapEncoder encoder = GetBitmapEncoderForFileExtension([filename]); // See documentation for which encoder to use when.
encoder.Frames.Add(BitmapFrame.Create(panelBitmap ));
encoder.Save(fs);
}
Save the container of the Canvas's to a bitmap, it'll pick up all the children.
Related
I am trying to save the content of a WPF Canvas in an image with the code:
Rect rect = new Rect(canvas.RenderSize);
RenderTargetBitmap rtb = new RenderTargetBitmap((int)rect.Right,
(int)rect.Bottom,
96d,
96d,
PixelFormats.Default);
rtb.Render(canvas);
BitmapEncoder pngEncoder = new PngBitmapEncoder();
pngEncoder.Frames.Add(BitmapFrame.Create(rtb));
System.IO.MemoryStream ms = new System.IO.MemoryStream();
pngEncoder.Save(ms);
ms.Close();
System.IO.File.WriteAllBytes("D://logo.png", ms.ToArray());
The canvas has a default horizontal alignment of 'Center' and it seems like this is an issue as the rendered image is actually the size of the canvas but includes the blank left margin, only showing part of the actual canvas content. But the blank space isn't even part of the canvas itself.
I tried to wrap the canvas in a grid and put it in a center column but it still renders the blank space from the left. If I align the image left horizontally it indeed renders the whole canvas, but I kind of need it to be centered. Is there any way I can modify the code to only include the canvas content and not the margin?
In WPF ,we can use VisualBrush do some thing like ppt's left side.
But I see the VisualBrush may lost the line in Rectangle when I zoom the VisualBrush to a small size.Like the image:
You can see VisualBrush lost the Bottom line.
But what I want is like the below image:
When I try use the BitmapImage that use RenderTargetBitmap to get a image and use linear interpolation algorithm to zoom will get a clearness image.
Can I change VisualBrush's algorithm I think it may use neighborhood-pixels algorithm.
Are there any printscreen algorithm that have a good performance like VisualBrush.
When I change my search key to ViewBox ,I can find the same question as this one :how to avoid a single pixel line disappear in wpf?
There is a class named TransformedBitmap which can scale your RenderTargetBitmap with default scaling algorithm.
Use the code below:
public static BitmapSource ToBitmapSource(this Visual visual, Size size)
{
var bounds = VisualTreeHelper.GetDescendantBounds(visual);
var width = (int) Math.Round(bounds.Width);
var height = (int) Math.Round(bounds.Height);
var bitmap = new RenderTargetBitmap(width, height, 96.0, 96.0, PixelFormats.Pbgra32);
bitmap.Render(visual);
return new TransformedBitmap(bitmap, new ScaleTransform(size.Width / width, size.Height / height));
}
I've tried this method in my demo and got the result below. You may noticed that the small rectangle in the left-top corner lost nothing.
I have a canvas that contains an Image in which I dislay an existing BMP. I draw rectangles on the canvas and add these to the Children colllection. When I click save, I want to update the underlying BMP file.
The following code works, but the rectangle that gets drawn to the BMP is way smaller than what I drew. I guess there's some difference in the co-ordinates? Maybe I shouldn't be using System.Drawing?
using (Graphics g = Graphics.FromImage(image))
{
g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
foreach (var child in canvas.Children)
{
if (child is System.Windows.Shapes.Rectangle)
{
var oldRect = child as System.Windows.Shapes.Rectangle;
// need to do something here to make the new rect bigger as the scale is clearly different
var rect = new Rectangle((int)Canvas.GetLeft(oldRect), (int)Canvas.GetTop(oldRect), (int)oldRect.Width, (int)oldRect.Height);
g.FillRectangle(Brushes.Black, rect);
}
}
... code to save bmp
All suggestions welcome!
Thanks
Try using the System.Windows.Media.Imaging.RenderTargetBitmap Class (an example here).
Wpf uses Device Independent Graphics so you have to compensate for the DPI :
RenderTargetBitmap bmp = new RenderTargetBitmap((int)Canvas1.Width, (int)Canvas1.Height, 96, 96, PixelFormats.Default);
bmp.Render(Canvas1);
From Third Link:
There are two system factors that determine the size of text and graphics on your screen: resolution and DPI. Resolution describes the number of pixels that appear on the screen. As the resolution gets higher, pixels get smaller, causing graphics and text to appear smaller. A graphic displayed on a monitor set to 1024 x 768 will appear much smaller when the resolution is changed to 1600 x 1200.
Render a WPF control to a bitmap image is not a trivial task as I found out today. As I know now dealing with the parent control margin is a problem, as Rick Strahl wrote in his blog
http://www.west-wind.com/weblog/posts/2007/Sep/10/Rendering-a-WPF-Container-to-Bitmap
So far I am able to create bitmaps of any control that is visible inside a window but what I really need to do is create bitmaps of invisible controls. I just create them in code - simples shapes like rectangle and ellipse - and want to save them as bitmaps to disk.
For me this turn out to be a personal nightmare. Since my ActualHeight and ActualWidth is always 0 I use Height and Width instead. But all I get is a empty image in the size of my control.
How can I create a bitmap of any control without adding it to a visible window?
New elements haven't had their layout performed. You need to call Measure and Arrange on the control before you render it.
Canvas c = new Canvas();
Rectangle r = new Rectangle
{
Fill = Brushes.Orange,
Width = 200,
Height = 100
};
Ellipse e = new Ellipse
{
Fill = Brushes.DodgerBlue,
Width = 100,
Height = 100
};
Canvas.SetLeft(e, 150);
c.Children.Add(r);
c.Children.Add(e);
var size = new Size(250,100);
c.Measure(size);
c.Arrange(new Rect(size));
RenderTargetBitmap bmp = new RenderTargetBitmap(250, 100, 96, 96, PixelFormats.Pbgra32);
bmp.Render(c);
PngBitmapEncoder enc = new PngBitmapEncoder();
enc.Frames.Add(BitmapFrame.Create(bmp));
using(var s = File.OpenWrite("image.png"))
enc.Save(s);
I'm loading up some images (jpg, bmp, png etc.), doing some manipulation, and saving them back out again as jpg images. When I save PNG images with a transparent background, they are saved with a black background, and I'd really prefer it to be white. Is there a way to do this?
The important thing is that this is a 'UI-less' routine in a product, so I've not got much leeway when it comes to radically changing the way things are done. Surely there must be a 'set background = white' or something?
A short snippet of my code...
// Load the image
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.None;
image.UriSource = "SomeImage.png";
image.EndInit();
// Some manipulation of the image here...
image.Shake().Twist().ThrowItAllAbout();
// Save it back out, in a different format
using (FileStream stream = new FileStream("SomeOtherFile.jpg", FileMode.Create))
{
var encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create((BitmapSource)image));
encoder.Save(stream);
}
I've tried to replace transparent pixels directly (with WriteableBitmap CopyPixels/WritePixels), but this wont work with semi-transparent pixels (for example, shadows will produce black outline artifacts).
So I decided to draw my image on top of white rectangle before saving.
It works just fine:
public static BitmapSource ReplaceTransparency(this BitmapSource bitmap, Color color)
{
var rect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight);
var visual = new DrawingVisual();
var context = visual.RenderOpen();
context.DrawRectangle(new SolidColorBrush(color), null, rect);
context.DrawImage(bitmap, rect);
context.Close();
var render = new RenderTargetBitmap(bitmap.PixelWidth, bitmap.PixelHeight,
96, 96, PixelFormats.Pbgra32);
render.Render(visual);
return render;
}
Usage:
var white = bitmap.ReplaceTransparency(Colors.White);
var enc = new JpegBitmapEncoder();
enc.Frames.Add(BitmapFrame.Create(white));
using (var fs = File.Create(filename))
enc.Save(fs);
There is no simple method to this, further you make some false assumptions when saying that transparent PNGs will be saved with a black background, that is not the case for all PNGs and it is solely dependent on the program that was used for saving the PNG.
If you make a pixel completely transparent that does not necessarily purge all its color from it, the pixel still has its three colour channels, e.g. pictures of the IPU certainly contain colour information, if you use your code to convert that image you'll see it.
One way to set all completely transparent pixels to white would be to get all the pixels as byte array, check the alpha channel of every pixel and if it is 0 you set every other channel of that pixel to 255.