I have this code in Silverlight:
Image image = new Image();
BitmapImage bitmapImage= TheDatasourceManager.GetBitmapImage("blackPencil");
image.Source = bitmapImage;
image.Stretch = Stretch.None;
image.HorizontalAlignment = HorizontalAlignment.Left;
image.VerticalAlignment = VerticalAlignment.Top;
image.Margin = new Thickness(88, 88, 0, 0);
grid.Children.Add(image);
Now I want to find out the height of the image.
in WPF I can get it with image.Source.Height but this is not available in Silverlight
bitmapImage.Height doesn't exist either
when I debug and examine the image object, I eventually get to PixelHeight which has an accurate height, but I can't seem to access it
I find image.ActualHeight but it is 0.
How can I get the height of the image?
I finally found it, it's just bitmapImage.PixelHeight. Since I'm not stretching it, seems to work fine.
Related
I've tried a few different approaches to this, but can't seem to get a combination that works.
Creating WPF app in C#, Visual Studio.
System.Windows.Shapes.Polyline works really nicely to draw into a Canvas in real-time, but I want to be able to draw in higher resolution onto a non-visual component that I can then render onto an Image.
If I create a Polyline on a Canvas that's visible in the UI, this works fine:
// Make rendertarget size of full page
RenderTargetBitmap rtb = new RenderTargetBitmap((int)wPage, (int)hPage, 96, 96, PixelFormats.Default);
// Render the polyline
rtb.Render(lineVirt);
// Apply to background image
imgBG.Source = rtb;
But if I create a Polyline on a Canvas that's not visible in the UI, then nothing renders to the image. This is probably fair enough. My guess is that the component recognises that it's not visible and therefore doesn't bother to render.
I've considered putting the Canvas somewhere in the UI buried under other controls, but that seems like a horrible kind of hack.
Essentially, all I need is a clean and fast way to draw a multi-point line of a specified width and color onto an Image. I thought that Polyline would work well, but only seems to work in a visible container.
What are my options?
You do not need a rendered Canvas or any other visible Panel at all.
Just use basic drawing primitives available at the Visual layer.
The DrawGeometry method below draws a Geometry onto a BitmapSource, using the bitmap's rendered size, i.e. the size that takes its DPI into account, and returns the resulting BitmapSource.
public static BitmapSource DrawGeometry(
BitmapSource source, Pen pen, Geometry geometry)
{
var visual = new DrawingVisual();
var rect = new Rect(0, 0, source.Width, source.Height);
using (var dc = visual.RenderOpen())
{
dc.DrawImage(source, rect);
dc.DrawGeometry(null, pen, geometry);
}
var target = new RenderTargetBitmap(
(int)rect.Width, (int)rect.Height, 96, 96, PixelFormats.Default);
target.Render(visual);
return target;
}
In order to draw in the bitmap's pixel units and hence ignore its DPI, modify the method like this:
var rect = new Rect(0, 0, source.PixelWidth, source.PixelHeight);
using (var dc = visual.RenderOpen())
{
dc.DrawRectangle(new ImageBrush(source), null, rect);
dc.DrawGeometry(null, pen, geometry);
}
The following method uses the above to draw a polyline as an IEnumerable<Point>.
public static BitmapSource DrawPolyline(
BitmapSource source, Pen pen, IEnumerable<Point> points)
{
var geometry = new PathGeometry();
if (points.Count() >= 2)
{
var figure = new PathFigure { StartPoint = points.First() };
figure.Segments.Add(new PolyLineSegment(points.Skip(1), true));
geometry.Figures.Add(figure);
}
return DrawGeometry(source, pen, geometry);
}
It would be used like
var source = new BitmapImage(new Uri(...));
var pen = new Pen
{
Brush = Brushes.Blue,
Thickness = 2,
};
var points = new List<Point>
{
new Point(100, 100),
new Point(1000, 100),
new Point(1000, 1000),
new Point(100, 1000),
new Point(100, 100),
};
image.Source = DrawPolyline(source, pen, points);
Your canvas needs a size, so someone or something has to Arrange it. That might already be enough to get it to render, but the only reliable way of rendering arbitrary visuals to a bitmap is to actually place them in the visual tree of a window that's displayed and thus laid out by WPF. You can then render to the bitmap in a deferred task at ContextIdle priority to ensure that layout is complete.
I'm using a DataGrid to represent some data in a WPF application. In a feature where I'm saving a particular WPF Window which has the DataGrid into a PDF using PDFSharp, I'm facing an issue that the DataGrid GridLines are not visible when the saved PDF is viewed in smaller viewing percentages.
(Refer attached images, only when the PDF view is set at 139%, the GridLines are visible. However, in smaller viewing %, some grid lines get omitted.)
Here's the PDF Saving Code:-
MemoryStream lMemoryStream = new MemoryStream();
Package package = Package.Open(lMemoryStream, FileMode.Create);
var doc = new System.Windows.Xps.Packaging.XpsDocument(package);
XpsDocumentWriter writer = System.Windows.Xps.Packaging.XpsDocument.CreateXpsDocumentWriter(doc);
VisualBrush sourceBrush = new VisualBrush(this);
DrawingVisual drawingVisual = new DrawingVisual();
using (var drawingContext = drawingVisual.RenderOpen())
{
drawingContext.DrawRectangle(sourceBrush, null, new Rect(new Point(0, 0), new Point(this.ActualWidth, this.ActualHeight)));
}
writer.Write(drawingVisual);
doc.Close();
package.Close();
var pdfXpsDoc = PdfSharp.Xps.XpsModel.XpsDocument.Open(lMemoryStream);
XpsConverter.Convert(pdfXpsDoc, sFileName, 0);
I believe it has to do with the quality with which the visual is drawn. Then I tried this snippet where I'm using DrawImage to make the visual at a higher resolution. Here's the snippet:-
MemoryStream lMemoryStream = new MemoryStream();
Package package = Package.Open(lMemoryStream, FileMode.Create);
var doc = new System.Windows.Xps.Packaging.XpsDocument(package);
XpsDocumentWriter writer = System.Windows.Xps.Packaging.XpsDocument.CreateXpsDocumentWriter(doc);
double dpiScale = 600.0 / 96.0;
var renderBitmap = new RenderTargetBitmap(Convert.ToInt32(this.Width * dpiScale),
Convert.ToInt32(this.Height * dpiScale),
600.0,
600.0,
PixelFormats.Pbgra32);
renderBitmap.Render(this);
var visual = new DrawingVisual();
using (var dc = visual.RenderOpen())
{
dc.DrawImage(renderBitmap, new Rect(0, 0, this.Width, this.Height));
}
writer.Write(visual);
doc.Close();
package.Close();
var pdfXpsDoc = PdfSharp.Xps.XpsModel.XpsDocument.Open(lMemoryStream);
XpsConverter.Convert(pdfXpsDoc, _pdfFileName, 0);
This snippet is working as in the grid lines are visible even in smaller viewing percentages but it makes my application stuck at the PDF save operation and also it throws System.OutofMemoryException with message "Insufficient memory to continue the execution of the program." However, the application doesn't crash.
To check the behavior of PDF viewer, I generated a table with multiple rows and columns in MS Word and saved it as a PDF. In that case, the table grid lines are clearly visible even at small viewing percentages.
Can anyone help me with this?
I assume the first code snippet creates a table in vector format (you do not supply a PDF that allows to verify this).
The second code snippet attempts to create a bitmap image (raster format).
Either way: with both vector and raster images it depends on the PDF viewer whether thin lines are visible. Adobe Reader has many options (like "Enhance thin lines", "Smooth line art", "Smooth images") that will have an effect on the actual display - to be set on the client computer, nothing to be set in the PDF.
I assume your test with MS Word also created a table in vector format, but maybe with thicker lines. So this test proofs nothing.
I had the same problem with disappearing grid lines when zooming out a PDF created with WPF.
The problem was that the TextBox objects in the Grid cells had a default background color (white) and a border color (black), and both were painted in the same place when zooming out. The solution was to not have a background at all, by setting the background to Transparent.
TextBox tx = new TextBox();
tx.Text = "X";
tx.SetValue(Grid.RowProperty, row);
tx.SetValue(Grid.ColumnProperty, col);
tx.BorderThickness = new Thickness(0.3, 0.3, 0, 0);
tx.BorderBrush = System.Windows.Media.Brushes.Black;
tx.Background = Brushes.Transparent;
grid.Children.Add(tx);
But what if you want to have some background in the grid cell? Then the solution is to add a separate Border object to the same Grid cell, and use Zindex to make sure that the Border object is painted in front of the other content.
TextBox tx = new TextBox();
tx.Text = "X";
tx.SetValue(Grid.RowProperty, row);
tx.SetValue(Grid.ColumnProperty, col);
tx.BorderThickness = new Thickness(0);
tx.Background = Brushes.LightPink;
grid.Children.Add(tx);
Border ct = new Border();
ct.SetValue(Grid.RowProperty, row);
ct.SetValue(Grid.ColumnProperty, col);
ct.BorderThickness = new Thickness(0.3, 0.3, 0, 0);
ct.BorderBrush = System.Windows.Media.Brushes.Black;
ct.Background = Brushes.Transparent;
ct.HorizontalAlignment = HorizontalAlignment.Stretch;
ct.VerticalAlignment = VerticalAlignment.Stretch;
Grid.SetZIndex(ct, 100);
grid.Children.Add(ct);
Also, UseLayoutRounding must be set to false (false is default). Otherwise lines with Thickness 0.5 or lower will disappear completely.
I know how to convert bitmap into BitmapImage from Load a WPF BitmapImage from a System.Drawing.Bitmap
But when my bitmap is changing, and I wanna the source of image, i.e. the BitmapImage will change with the bitmap in real time, how can I do it?
My code for generating bitmap is like this:
Rectangle rect = new Rectangle(0, 0, 300, 300);
bitmap = new Bitmap(rect.Width, rect.Height);
Graphics g = Graphics.FromImage(bitmap);
DoWithGraphics(g, rect, pictureelements);
DoWithGraphics is a series of g.FillRectangle
Thanks!
It shouldn't be a problem to display the bitmap in an Image control and update the Source property whenever the bitmap changes.
Im trying to create some chart images without ever displaying those charts on the screen. I'v been at this for quite a while and tried a lot of different things but nothing seems to work. The code works perfectly if I display the chart in a window first, but if I don't display it in a window, the bitmap is just white with a black border (no idea why).
I have tried adding the chart to a border before rendering and giving the border a green borderBrush. In the bitmap, I see the green borderBrush then the black border and white background but no chart. The Chart is not contained in a black boarder so I don't know where that is coming from.
I have tried adding the chart to a window without calling window.Show() and again I just get the black boarder and white background. However if I call window.Show() the bitmap contains the chart.
I have tried using a drawingVisual as explained here, same result.
Here is the code (not including adding the element to a border or window):
private static BitmapSource CreateElementScreenshot(FrameworkElement element, int dpi)
{
if (!element.IsMeasureValid)
{
Size size = new Size(element.Width, element.Height);
element.Measure(size);
element.Arrange(new Rect(size));
}
element.UpdateLayout();
var scale = dpi/96.0;
var renderTargetBitmap = new RenderTargetBitmap
(
(int)(scale * element.RenderSize.Width),(int)(scale * element.RenderSize.Height),dpi,dpi,PixelFormats.Default
);
// this is waiting for dispatcher to perform measure, arrange and render passes
element.Dispatcher.Invoke(((Action)(() => renderTargetBitmap.Render(element))), DispatcherPriority.Render);
return renderTargetBitmap;
}
Note: The chart is a ContentControl.
Is there anyway I can get the chart to render without displaying it in a window first?
Calling element.ApplyTemplate() did the trick.
If someone has similar problems with rendering RenderTargetBitmap (getting white / empty image) items that are in StackPanel you can temporary move them to Grid, then render and put it back in StackPanel
Grid grid = new System.Windows.Controls.Grid() { Background = Brushes.White, Width = iWidth, Height = iHeight };
Panel panel = plot.Parent as Panel;
if (panel != null)
{
panel.Children.Remove(plot);
grid.Children.Add(plot);
grid.Measure(new Size(iWidth, iHeight));
grid.Arrange(new Rect(new Size(iWidth, iHeight)));
}
plot.Measure(new Size(iWidth, iHeight));
plot.Arrange(new Rect(new Size(iWidth, iHeight)));
plot.ApplyTemplate();
plot.UpdateLayout();
grid.ApplyTemplate();
grid.UpdateLayout();
RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap(
iWidth,
iHeight,
96, 96, PixelFormats.Pbgra32);
renderTargetBitmap.Render(grid);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderTargetBitmap));
MemoryStream memoryStream = new MemoryStream();
encoder.Save(memoryStream);
bitmap = new System.Drawing.Bitmap(memoryStream);
if (panel != null)
{
grid.Children.Remove(plot);
panel.Children.Add(plot);
}
plot.Measure(new Size(iWidthBefore, iHeightBefore));
plot.Arrange(new Rect(new Size(iWidthBefore, iHeightBefore)));
plot.UpdateLayout();
For me, calling element.Arrange() was the missing piece.
In my Silverlight control, I am loading my background image from a stream:
BitmapImage img = new BitmapImage();
img.SetSource(stream);
Image background = new Image();
background.Source = img;
How can I find out the height of the bitmap image that was loaded from stream? None of the usual suspects (e.g., Property, DependencyProperty) seem to be available, neither on img, nor on background.
I would try:
img.Measure();
img.DesiredSize.Height;