Here is my code. I expect the viewbox to render according to size of the pdf canvas size (which is something like 750 by 600). However, when the image is saved to pdf, the image is severely cropped. Only the top/left details are saved. I know this is the case becase if I set page.Canvas.DrawImage's size parameter to various values, the image changes size, but it is always the same cropped image, so either RenderTargetBitmap is not rending the viewbox properly or the encoder is not encoding properly or the pdfimage.fromstream is not converting properly.
//.. set up the doc
var doc = new PdfDocument();
var page = doc.Pages.Add(PdfPageSize.Letter, new PdfMargins(30f), PdfPageRotateAngle.RotateAngle0, PdfPageOrientation.Landscape);
//.. render the viewbox (must be done or its elements don't scale)
vb.Measure(new System.Windows.Size(page.Canvas.ClientSize.Width, page.Canvas.ClientSize.Height));
vb.Arrange(new Rect(0, 0, page.Canvas.ClientSize.Width, page.Canvas.ClientSize.Height));
vb.UpdateLayout();
RenderTargetBitmap render = new RenderTargetBitmap((int)vb.ActualWidth, (int)vb.ActualHeight, 150, 150, PixelFormats.Pbgra32);
render.Render(vb);
//.. save the render to memory
var stream = new MemoryStream();
BitmapEncoder encoder = new PngBitmapEncoder();
var frame = BitmapFrame.Create(render);
encoder.Frames.Add(frame);
encoder.Save(stream);
//.. use spire pdf to convert the stream to an image
var img = PdfImage.FromStream(stream);
float widthFitRate = img.PhysicalDimension.Width / page.Canvas.ClientSize.Width;
float heightFitRate = img.PhysicalDimension.Height / page.Canvas.ClientSize.Height;
float fitRate = Math.Max(widthFitRate, heightFitRate);
float fitWidth = img.PhysicalDimension.Width / fitRate;
float fitHeight = img.PhysicalDimension.Height / fitRate;
page.Canvas.DrawImage(img, new PointF(0,0), new SizeF(img.PhysicalDimension.Width, img.PhysicalDimension.Height ));
doc.SaveToFile(#"C:\test.pdf");
doc.Close();
Related
I have got a WPF application, and I would like to save a Canvas to a file with a correct DPI value. The Canvas size is the real physical size, eg. 20x10 cm, at 300 DPI, so it's 2362x1181.
I draw images and shapes with DrawingVisual, then I create a RenderTargetBitmap. The size of the rtb is the size of the Canvas. The dpiX and dpiY are 96. I got correct image resolution only with 96 DPI. When I set it to 300, the canvas become upscale, and cropped to 2362x1181. So, it's not good. I tried to modify the canvas width and height value by dpi factor, but didn't work either.
After the RenderTargetBitmap, I use BitmapEncoder, BitmapFrame, and BinaryWriter. See code below. Working great, but the image DPI value will be 96. I've read a tons of topics about reading DPI, resaving image, using SetResolution, but I don't want to loose quality, I don't want to read file and resave it, I don't want to change pixel width/height, etc.
I just really want to save a DrawingVisual with a given DPI. I could write EXIF data for "X Resolution" (uint=282) and "Y Resoulution" (uint=283), but that didn't affect the Image DPI settings, so eg. Photoshop will read 96, not 300.
BitmapEncoder encoder = new JpegBitmapEncoder();
BitmapFrame bFrame = BitmapFrame.Create(rtb, null, meta, icc);
encoder.Frames.Add(bFrame);
using (var stream = new MemoryStream())
{
encoder.Save(stream);
byte[] imageData = stream.ToArray();
using (FileStream fs = new FileStream(path, ...)
{
BinaryWriter bw = new BinaryWriter(fs);
bw.Write(imageData);
bw.Close();
}
}
The following method saves a UIElement to a JPEG file with the specified DPI value:
public static void SaveElement(UIElement element, double dpi, string path)
{
var visual = new DrawingVisual();
var width = element.RenderSize.Width;
var height = element.RenderSize.Height;
using (var context = visual.RenderOpen())
{
context.DrawRectangle(new VisualBrush(element), null,
new Rect(0, 0, width, height));
}
var bitmap = new RenderTargetBitmap(
(int)(width * dpi / 96), (int)(height * dpi / 96),
dpi, dpi, PixelFormats.Default);
bitmap.Render(visual);
var encocer = new JpegBitmapEncoder();
encocer.Frames.Add(BitmapFrame.Create(bitmap));
using (var stream = File.OpenWrite(path))
{
encocer.Save(stream);
}
}
Alternatively, apply the DPI scaling to the DrawingVisual:
public static void SaveElement(UIElement element, double dpi, string path)
{
var visual = new DrawingVisual();
var width = element.RenderSize.Width;
var height = element.RenderSize.Height;
using (var context = visual.RenderOpen())
{
context.DrawRectangle(new VisualBrush(element), null,
new Rect(0, 0, width / dpi * 96, height / dpi * 96));
}
var bitmap = new RenderTargetBitmap(
(int)width, (int)height, dpi, dpi, PixelFormats.Default);
bitmap.Render(visual);
var encocer = new JpegBitmapEncoder();
encocer.Frames.Add(BitmapFrame.Create(bitmap));
using (var stream = File.OpenWrite(path))
{
encocer.Save(stream);
}
}
It seems the solution is the Bitmap SetResolution, after all. I tested it, and it looks like not affect the image quality after the JpegBitmapEncoder()! And the image resolution is untouched, keep the metadata, only the DPI will change!
Helped this documentation: https://learn.microsoft.com/en-us/dotnet/framework/winforms/advanced/how-to-set-jpeg-compression-level
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.QualityLevel = 100;
BitmapFrame bFrame = BitmapFrame.Create(rtb, null, meta, icc);
encoder.Frames.Add(bFrame);
using (var stream = new MemoryStream())
{
encoder.Save(stream);
using (var bmpOutput = new System.Drawing.Bitmap(stream))
{
System.Drawing.Imaging.ImageCodecInfo myEncoder = GetEncoder(ImageFormat.Jpeg);
var encoderParameters = new System.Drawing.Imaging.EncoderParameters(1);
encoderParameters.Param[0] = new System.Drawing.Imaging.EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
bmpOutput.SetResolution(300.0f, 300.0f);
bmpOutput.Save(filePath, myEncoder, encoderParameters);
}
}
I tried to apply DPI scaling to the DrawingVisual, but it produced less image quality. :( Also I tried to set the canvas to 756x378 (the 96 DPI size), then set the RenderTargetBitmap to 2362x1181 and 300 DPI, but that produced less image quality too. So, it seems any downscale then upscale or upscale then downscale are not the best solution. The DrawingVisual must be in the final render size.
I have an application that gets and saves a picture of an open window. Unfortunately, when the dimensions of the image and window match, the image quality is unacceptably low. I've increased the size the of the RenderTargetBitmap, which has increased the image quality, however, now the dimensions of the resulting image are too large for my purposes.
My question is this: Is there any way to resize the resulting RenderTargetBitmap to the original dimensions of the window? And is it possible to do this without a corresponding loss in image quality? Here's the code I have now.
public static RenderTargetBitmap GetReportImage(Grid view)
{
// Increased dimensions to increase image quality
Size size = new Size(view.ActualWidth * 2, view.ActualHeight * 2 );
// The dimensions that I want to convert back to.
_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);
// Can I resize result to _size.Width and _size.Height?
return result;
}
Thanks in advance for any help.
To resize back you can do:
var resized = new TransformedBitmap(bmp,
new ScaleTransform(newWidth / bmp.PixelWidth, newHeight / bmp.PixelHeight));
You can also try to scale both dimensions and dpi, like this:
public static RenderTargetBitmap RenderBitmap(FrameworkElement visualToRender, double scale) {
RenderTargetBitmap bmp = new RenderTargetBitmap
(
(int) (scale*(visualToRender.ActualWidth + 1)),
(int) (scale*(visualToRender.ActualHeight + 1)),
scale*96,
scale*96,
PixelFormats.Pbgra32
);
bmp.Render(visualToRender);
return bmp;
}
Updated to answer your comment. So I suppose you need to save the result somewhere, probably to jpeg or png. Both RenderTargetBitmap and TransformedBitmap inherit from BitmapSource, so both you can encode to image like this:
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bmp));
using (Stream s = File.Create("test.png")) {
encoder.Save(s);
}
Here "bmp" can be both RenderTargetBitmap and TrasnformedBitmap
Going bonkers here trying to do this in WPF (with all the new image manipulation tools), but can't seem to find a working solution. So far, all the solutions are drawing them on the screen or making multiple saves, but I need to do this completely in memory.
Basically, I want to load a large jpeg into memory, resize it smaller (in memory), save as a small PNG file.
I can load the jpeg file into a BitMap object, fine. after that, I'm stumped.
I found this function that looks like it does the trick but it requires an ImageSource (unfortunately, I can't find a way to convert my in-memory BitMap object to an ImageSource that does not yield a NULL exception.)
private static BitmapFrame CreateResizedImage(ImageSource source, int width, int height, int margin)
{
dynamic rect = new Rect(margin, margin, width - margin * 2, height - margin * 2);
dynamic #group = new DrawingGroup();
RenderOptions.SetBitmapScalingMode(#group, BitmapScalingMode.HighQuality);
#group.Children.Add(new ImageDrawing(source, rect));
dynamic drawingVisual = new DrawingVisual();
using (drawingContext == drawingVisual.RenderOpen())
{
drawingContext.DrawDrawing(#group);
}
// Resized dimensions
// Default DPI values
dynamic resizedImage = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Default);
// Default pixel format
resizedImage.Render(drawingVisual);
return BitmapFrame.Create(resizedImage);
}
With WPF it's as easy as this:
private void ResizeImage(string inputPath, string outputPath, int width, int height)
{
var bitmap = new BitmapImage();
using (var stream = new FileStream(inputPath, FileMode.Open))
{
bitmap.BeginInit();
bitmap.DecodePixelWidth = width;
bitmap.DecodePixelHeight = height;
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.StreamSource = stream;
bitmap.EndInit();
}
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
using (var stream = new FileStream(outputPath, FileMode.Create))
{
encoder.Save(stream);
}
}
You may consider to only set one of DecodePixelWidth and DecodePixelHeight in order to preserve the original image's aspect ratio.
I am taking an image of a wpf control using this code:
BitmapEncoder imgEncoder = new PngBitmapEncoder();
RenderTargetBitmap bmpSource = new RenderTargetBitmap((int)element.ActualWidth, (int)element.ActualHeight, 150, 150, PixelFormats.Pbgra32);
bmpSource.Render(element);
imgEncoder.Frames.Add(BitmapFrame.Create(bmpSource));
using (MemoryStream ms = new MemoryStream())
{
imgEncoder.Save(ms);
bytes = ms.ToArray();
ms.Position = 0;
Image i = Image.FromStream(ms);
i.Save(#"C:\" + Guid.NewGuid().ToString() + "LARGE.png");
}
The trouble is ActualHeight/Width property gives the rendered height and width i.e. the displayed part. I want to save an image of the whole control even if some of the control is not visible on the screen i.e. it is placed in a scrollviewer.
How can I get the full size / height of a control so the bmpSource.Render() renders the whole control to an image?
private static void SaveUsingEncoder(string fileName, FrameworkElement UIElement, BitmapEncoder encoder)
{
int height = (int)UIElement.ActualHeight;
int width = (int)UIElement.ActualWidth;
// These two line of code make sure that you get completed visual bitmap.
// In case your Framework Element is inside the scroll viewer then some part which is not
// visible gets clip.
UIElement.Measure(new System.Windows.Size(width, height));
UIElement.Arrange(new Rect(new System.Windows.Point(), new Point(width, height)));
RenderTargetBitmap bitmap = new RenderTargetBitmap(width,
height,
96, // These decides the dpi factors
96,// The can be changed when we'll have preview options.
PixelFormats.Pbgra32);
bitmap.Render(UIElement);
SaveUsingBitmapTargetRenderer(fileName, bitmap, encoder);
}
private static void SaveUsingBitmapTargetRenderer(string fileName, RenderTargetBitmap renderTargetBitmap, BitmapEncoder bitmapEncoder)
{
BitmapFrame frame = BitmapFrame.Create(renderTargetBitmap);
bitmapEncoder.Frames.Add(frame);
// Save file .
using (var stream = File.Create(fileName))
{
bitmapEncoder.Save(stream);
}
}
Call this function as
SaveUsingEncoder(fileName, frameworkElement, new PngBitmapEncoder());
Hope this will help.
You could try temporarily taking the control out of its context (might cause problems if bound),transform it to a visivible point or scroll it into view for rendering.
Hi i'm creating an image in memory from a Canvas using a PngBitmapEncoder.
public void CaptureGraphic()
{
Canvas canvas = new Canvas();
canvas.SnapsToDevicePixels = true;
canvas.Height = IMAGEHEIGHT;
canvas.Width = IMAGEWIDTH;
Draw(canvas);
canvas.Arrange(new Rect(0, 0, IMAGEWIDTH, IMAGEHEIGHT));
member.MemberImage = GetPngFromUIElement(canvas);
}
public static System.Drawing.Image GetPngFromUIElement(Canvas source)
{
int width = (int)source.ActualWidth;
int height = (int)source.ActualHeight;
if (width == 0)
width = (int)source.Width;
if (height == 0)
height = (int)source.Height;
RenderTargetBitmap bitmap = new RenderTargetBitmap(width, height, 96d, 96d, PixelFormats.Pbgra32);
bitmap.Render(source);
PngBitmapEncoder enc = new PngBitmapEncoder();
enc.Interlace = PngInterlaceOption.Off;
enc.Frames.Add(BitmapFrame.Create(bitmap));
System.IO.MemoryStream ms = new System.IO.MemoryStream();
enc.Save(ms);
System.Drawing.Image image = System.Drawing.Image.FromStream(ms);
ms.Flush();
ms.Dispose();
return image;
}
Then i'm sending the image to the printer using the GDI+ DrawImage() method. However the printed result is blurry.
I've tried to match the original canvas size to the printed size to avoid any scaling, equally i've tried to make the original considerably bigger so the scaled image retains the quality however the final printed image is always blurred.
Can anyone offer any suggestions/alternatives. I have a considerable GDI+ print routine already setup and moving to wpf documents is not an option just yet.
Thanks
You're capturing the bitmap at 96 DPI. Instead of using 96 in the constructor of the RenderTargetBitmap, try to match the DPI of your printer output. Alternatively, you could do the math and calculate the difference in width/height and rescale the image on the report accordingly (the result is the image on the report will appear smaller).
I had the same blurry result and have come up with the following piece of code, which applies the same idea with the offset, but using the Offset property on the DrawingVisual (since I was using DrawDrawing, which doesn't have an overload with the offset argument):
public static Image ToBitmap(Image source)
{
var dv = new DrawingVisual();
// Blur workaround
dv.Offset = new Vector(0.5, 0.5);
using (var dc = dv.RenderOpen())
dc.DrawDrawing(((DrawingImage)source.Source).Drawing);
var bmp = new RenderTargetBitmap((int)source.Width, (int)source.Height, 96, 96, PixelFormats.Pbgra32);
bmp.Render(dv);
var bitMapImage = new Image();
bitMapImage.Source = bmp;
bitMapImage.Width = source.Width;
bitMapImage.Height = source.Height;
return bitMapImage;
}
Think i've found the answer.
http://www.charlespetzold.com/blog/2007/12/High-Resolution-Printing-of-WPF-3D-Visuals.html
I just needed to scale up the image size along with the dpi and voila, massively increased file size!
I had the same problem. To avoid blurry text and lines, I had to draw everything with an offset of 0.5 in X and Y direction. E.g, an horizontal line could be
drawingContext.DrawLine(pen, new Point(10.5,10.5), new Point(100.5,10.5));
In my case, I was rendering to a RenderTargetBitmap in a different thread to improve the UI performance. The rendered bitmap is then frozen and drawn onto the UI using
drawingContext.DrawImage(bitmap, new Rect(0.5, 0, bitmap.Width, bitmap.Height));
Here, I needed an additional offset of 0.5, but (strangely) only in X direction, so that the rendered image did not look Blurry any more.