In my WPF application I have a ScrollViewer which is dynamically populated by DataBinding and the use of a UserControl. Imagine that my UserControl is something simple that only contains a label, and the value displayed in the label comes from a list. So when I run the application it looks like below:
As you can see there are multiple instances of the UserControl each with a different value that is dynamically populated. My goal is to export each of the UserControls as a separate PNG. My EXPORT PNG button click should do this.
So I looked around and found this example which works fine for exporting the entire content of the ScorllViewer.
So I tried modifying it to achieve my goal, and I do get it to work to a certain extent, but not quite what I want.
Here's my code:
private void ExportPNG()
{
var dir = Directory.GetCurrentDirectory();
var file = "ITEM_{0}.PNG";
var height = 100.0; // Height of the UserControl
var width = mainSV.ActualWidth;
for (int i = 1; i <= ItemList.Count; i++)
{
var path = System.IO.Path.Combine(dir, string.Format(file, i));
Size size = new Size(width, height);
UIElement element = mainSV.Content as UIElement;
element.Measure(size);
element.Arrange(new Rect(new Point(0, 0), size));
RenderTargetBitmap renderTarget = new RenderTargetBitmap((int)width, (int)height, 96, 96, PixelFormats.Pbgra32);
VisualBrush sourceBrush = new VisualBrush(element);
DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
using (drawingContext)
{
drawingContext.DrawRectangle(sourceBrush, null, new Rect(new Point(0, 0), new Point(width, height)));
}
renderTarget.Render(drawingVisual);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderTarget));
using (FileStream stream = new FileStream(path, FileMode.Create, FileAccess.Write))
{
encoder.Save(stream);
}
}
}
Now this exports 5 PNGs (when my list has 5 items), bu they all contain the image of the first element:
And I figure the problem is likely where I do the drawingContext.DrawRectangle() because my rectanble coordinates are always the same. So I tried changing it to the following, which I thought should work, but for some reason it just generates one PNG with the first UserControl and 4 empty PNGs.
using (drawingContext)
{
drawingContext.DrawRectangle(sourceBrush, null, new Rect(new Point(0, (i-1) * height), new Point(width, height + ((i-1) * height))));
}
And the result:
What am I doing wrong here?
If you would like to run the code please find it here.
Please check below two methods. I tested it and it is working very well.
1. First, find children from you Items controls
2. Convert them to PNGs.
private void ExportPNG()
{
var dir = Directory.GetCurrentDirectory();
var file = "ITEM_{0}.PNG";
var height = 100.0;
var width = 100.0;
var children = GetChildrenOfType<UCDisplayItem>(itC);
foreach (var item in children)
{
var path = System.IO.Path.Combine(dir, string.Format(file, DateTime.Now.Ticks));
Size size = new Size(width, height);
UIElement element = item as UIElement;
element.Measure(size);
element.Arrange(new Rect(new Point(0, 0), size));
RenderTargetBitmap renderTarget = new RenderTargetBitmap((int)width, (int)height, 96, 96, PixelFormats.Pbgra32);
VisualBrush sourceBrush = new VisualBrush(element);
DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
using (drawingContext)
{
drawingContext.DrawRectangle(sourceBrush, null, new Rect(new Point(0, 0), new Point(width, height)));
//drawingContext.DrawRectangle(sourceBrush, null, new Rect(new Point(0, (i - 1) * height), new Point(width, height + ((i - 1) * height))));
}
renderTarget.Render(drawingVisual);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderTarget));
using (FileStream stream = new FileStream(path, FileMode.Create, FileAccess.Write))
{
encoder.Save(stream);
}
}
}
public List<T> GetChildrenOfType<T>( DependencyObject depObj)
where T : DependencyObject
{
var result = new List<T>();
if (depObj == null) return null;
var queue = new Queue<DependencyObject>();
queue.Enqueue(depObj);
while (queue.Count > 0)
{
var currentElement = queue.Dequeue();
var childrenCount = VisualTreeHelper.GetChildrenCount(currentElement);
for (var i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(currentElement, i);
if (child is T)
result.Add(child as T);
queue.Enqueue(child);
}
}
return result;
}
RESULT
Related
So we have a WPF application that shows some charts created using the Live Charts library. However I got the task to render the charts in the background, and save them to a PNG image without displaying a WPF window. I found an article online that takes a UserControl and does exactly that, but trying it myself I only get a transparent image of 80 by 50 pixels.
Unfortunately I am not very familiar with the WPF internals of rendering, so any help is really appreciated. This is my code rendered down to the bare minimum with the simplest chart example from LiveChart itself.
using System;
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using LiveCharts.Wpf;
using LiveCharts.Geared;
using LiveCharts;
namespace Screenshot2Pdf {
class Program {
[STAThread]
static void Main(string[] args) {
var Series = new SeriesCollection();
var r = new Random();
for (var i = 0; i < 30; i++) // 30 series
{
var trend = 0d;
var values = new double[10000];
for (var j = 0; j < 10000; j++) // 10k points each
{
trend += (r.NextDouble() < .8 ? 1 : -1) * r.Next(0, 10);
values[j] = trend;
}
var series = new LineSeries {
Values = values.AsGearedValues().WithQuality(Quality.Low),
Fill = Brushes.Transparent,
StrokeThickness = .5,
PointGeometry = null //use a null geometry when you have many series
};
Series.Add(series);
}
var chart = new CartesianChart();
chart.Series = Series;
chart.BeginInit();
chart.EndInit();
// Measure and arrange the tile
chart.Measure(new System.Windows.Size {
Height = double.PositiveInfinity,
Width = double.PositiveInfinity
});
chart.Arrange(new System.Windows.Rect(0, 0,
chart.DesiredSize.Width,
chart.DesiredSize.Height));
chart.UpdateLayout();
RenderTargetBitmap rtb = new RenderTargetBitmap((int)chart.DesiredSize.Width, (int)chart.DesiredSize.Height, 96, 96, PixelFormats.Pbgra32);
rtb.Render(chart);
PngBitmapEncoder png = new PngBitmapEncoder();
png.Frames.Add(BitmapFrame.Create(rtb));
MemoryStream stream = new MemoryStream();
png.Save(stream);
var image = System.Drawing.Image.FromStream(stream);
image.Save(#"D:\bitmap.png");
}
}
}
Oké I am not sure why. As said before I am not very familiar with WPF's internal rendering mechanism and what triggers it, but the solution is to wrap the Chart in a Viewbox. The following code does the trick:
var viewbox = new Viewbox();
viewbox.Child = chart;
viewbox.Measure(chart.RenderSize);
viewbox.Arrange(new Rect(new Point(0, 0), chart.RenderSize));
chart.Update(true, true); //force chart redraw
viewbox.UpdateLayout();
// RenderTargetBitmap rtb = new RenderTargetBitmap((int)chart.DesiredSize.Width, (int)chart.DesiredSize.Height, 96, 96, PixelFormats.Pbgra32);
var rtb = new RenderTargetBitmap(500, 500, 96, 96, PixelFormats.Default);
rtb.Render(chart);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(rtb));
using (var stream = File.OpenWrite(#"D:\bitmap.png")) {
encoder.Save(stream);
}
Is there any way to print a screen capture of a wpf window?
I tried a lot of stuff, but either it's too big for A4 size or it's cropped of because there are no margins set.
Here the code i tried:
Sample1:
private void btnPrintStanzbild_Click(object sender, RoutedEventArgs e)
{
Visual target = gRight;
Rect bounds = VisualTreeHelper.GetDescendantBounds(target);
RenderTargetBitmap renderTarget = new RenderTargetBitmap((Int32)bounds.Width, (Int32)bounds.Height, 96, 96, PixelFormats.Pbgra32);
DrawingVisual visual = new DrawingVisual();
using (DrawingContext context = visual.RenderOpen())
{
VisualBrush visualBrush = new VisualBrush(target);
context.DrawRectangle(visualBrush, null, new Rect(new Point(), bounds.Size));
}
renderTarget.Render(visual);
PngBitmapEncoder bitmapEncoder = new PngBitmapEncoder();
BitmapImage bi = new BitmapImage();
bitmapEncoder.Frames.Add(BitmapFrame.Create(renderTarget));
using (MemoryStream stm = new MemoryStream())
{
bitmapEncoder.Save(stm);
bi.BeginInit();
bi.CacheOption = BitmapCacheOption.OnLoad;
bi.StreamSource = stm;
bi.EndInit();
}
var vis = new DrawingVisual();
var dc = vis.RenderOpen();
dc.DrawImage(bi, new Rect { Width = bi.Width, Height = bi.Height });
dc.Close();
var pdialog = new PrintDialog();
if (pdialog.ShowDialog() == true)
{
var queue = pdialog.PrintQueue;
var area = queue.GetPrintCapabilities(pdialog.PrintTicket)
.PageImageableArea;
pdialog.PrintVisual(vis, "My Image");
}
}
Sample2:
PrintDialog pdialog = new PrintDialog();
if (pdialog.ShowDialog() == true)
{
var queue = pdialog.PrintQueue;
var area = queue.GetPrintCapabilities(pdialog.PrintTicket)
.PageImageableArea;
pdialog.PrintVisual(this, "My Image");
}
Sample 3:
Link to sample 3
private void Print()
{
PrintDialog pdialog = new PrintDialog();
if (pdialog.ShowDialog() == true)
{
// Save current layout
Transform origTransform = LayoutTransform;
Size oldWindowSize = new Size(ActualWidth, ActualHeight);
// Get printer caps
PrintCapabilities capabilities = pdialog.PrintQueue.GetPrintCapabilities(pdialog.PrintTicket);
// Get size of the printer page
var sz = new Size(capabilities.PageImageableArea.ExtentWidth, capabilities.PageImageableArea.ExtentHeight);
// Calculate zoom level of window
double ratio = Math.Min(sz.Width / ActualWidth, sz.Height / ActualHeight);
//if (ratio < 1) // Uncomment this line if you dont want zoom out small window
{
LayoutTransform = new ScaleTransform(ratio, ratio);
}
Measure(sz);
Arrange(new Rect(new Point(0,0), sz));
pdialog.PrintVisual(this, "My Image");
// Store old layout
LayoutTransform = origTransform;
Measure(oldWindowSize);
Arrange(new Rect(new Point(0, 0), oldWindowSize));
}
}
I have the next code:
private static void SnapShotPNG(ListView source, string destination, int zoom)
{
try
{
double actualHeight = source.ActualHeight;
double actualWidth = source.ActualWidth;
double renderHeight = actualHeight * zoom;
double renderWidth = actualWidth * zoom;
RenderTargetBitmap renderTarget = new RenderTargetBitmap((int)renderWidth, (int)renderHeight, 96, 96, PixelFormats.Pbgra32);
VisualBrush sourceBrush = new VisualBrush(source);
DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
using (drawingContext)
{
drawingContext.PushTransform(new ScaleTransform(zoom, zoom));
drawingContext.DrawRectangle(sourceBrush, null, new Rect(new Point(0, 0), new Point(actualWidth, actualHeight)));
}
renderTarget.Render(drawingVisual);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderTarget));
using (FileStream stream = new FileStream(destination, FileMode.Create, FileAccess.Write))
{
encoder.Save(stream);
}
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
}
It saves a given source to an image, it works fine. But it save only visible part of control (in my case, only visible items of ListView). How can i save snapshot of whole items in ListView?
I've changed your first two lines, by adding Arrange and Measure methods, which allow the control to render in memory. I've assumed, that your control doesn't scroll horizontally and kept the width as it was, since otherwise it will use minimal width required for its largest child. You can change it.
Here is your method.
private static void SnapShotPNG(ListView source, string destination, int zoom)
{
try
{
double actualWidth = source.ActualWidth;
source.Measure(new Size(source.ActualWidth, Double.PositiveInfinity));
source.Arrange(new Rect(0, 0, actualWidth, source.DesiredSize.Height));
double actualHeight = source.ActualHeight;
double renderHeight = actualHeight * zoom;
double renderWidth = actualWidth * zoom;
RenderTargetBitmap renderTarget = new RenderTargetBitmap((int)renderWidth, (int)renderHeight, 96, 96, PixelFormats.Pbgra32);
VisualBrush sourceBrush = new VisualBrush(source);
DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
using (drawingContext)
{
drawingContext.PushTransform(new ScaleTransform(zoom, zoom));
drawingContext.DrawRectangle(sourceBrush, null, new Rect(new Point(0, 0), new Point(actualWidth, actualHeight)));
}
renderTarget.Render(drawingVisual);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderTarget));
using (FileStream stream = new FileStream(destination, FileMode.Create, FileAccess.Write))
{
encoder.Save(stream);
}
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
}
I am using a DrawingContext to draw images. I then render the result to a RenderTargetBitmap. I also render a Canvas to the same RenderTargetBitmap. Even though the pixel boundaries are crisp on screen, they become blurred and fuzzy when saved.
In the screenshot below, you can see the issue (with BitmapScalingMode = NearestNeighbor).
Here it is with BitmapScalingMode = HighQuality. It's smoother but not crisp and clean.
Here is the relevant section of my code. You can see that I tried setting the RenderOptions at multiple places but it seems to have no effect.
DrawingVisual drawingVisual = new DrawingVisual();
RenderTargetBitmap result = new RenderTargetBitmap((int)size.Width, (int)size.Height, 96, 96, PixelFormats.Pbgra32);
RenderOptions.SetBitmapScalingMode(drawingVisual, BitmapScalingMode.NearestNeighbor); // This forces the scaling to be on even-pixel boundaries
RenderOptions.SetBitmapScalingMode(drawCanvas, BitmapScalingMode.NearestNeighbor); // This forces the scaling to be on even-pixel boundaries
RenderOptions.SetBitmapScalingMode(result, BitmapScalingMode.NearestNeighbor); // This forces the scaling to be on even-pixel boundaries
using (DrawingContext context = drawingVisual.RenderOpen()) {
context.DrawRectangle(Brushes.Black, null, new Rect(new Point(), new Size(size.Width, size.Height)));
if (layers.Count >= 1 && layers[0].LayerImage != null && layers[0].LayerImage.Source != null && gridImage.Children[1].Visibility == System.Windows.Visibility.Visible)
context.DrawImage(layers[0].LayerImage.Source, new Rect(size)); // Draw first image.
context.Close();
}
result.Render(drawingVisual);
drawCanvas.Measure(drawCanvas.RenderSize);
drawCanvas.Arrange(new Rect(drawCanvas.RenderSize));
for (int i = 0; i < drawCanvas.Children.Count; i++) {
RenderOptions.SetBitmapScalingMode(drawCanvas.Children[i], BitmapScalingMode.NearestNeighbor); // This forces the scaling to be on even-pixel boundaries
}
result.Render(drawCanvas);
BitmapEncoder encoder = new PngBitmapEncoder();
if (result!= null) {
encoder.Frames.Add(BitmapFrame.Create((BitmapSource)result));
encoder.Save(fileStream);
}
I don't know if you have fixed that but that function works very good on my side.
public BitmapSource SnapShotPNG(UIElement source)
{
double actualWidth = source.RenderSize.Width;
double actualHeight = source.RenderSize.Height;
RenderTargetBitmap renderTarget = new RenderTargetBitmap((int)actualWidth, (int)actualHeight, 96, 96, PixelFormats.Pbgra32);
DrawingVisual visual = new DrawingVisual();
using (DrawingContext context = visual.RenderOpen())
{
VisualBrush sourceBrush = new VisualBrush(source);
context.DrawRectangle(sourceBrush, null, new Rect(new Point(0, 0), new Point(actualWidth, actualHeight)));
}
source.Measure(source.RenderSize); //Important
source.Arrange(new Rect(source.RenderSize)); //Important
renderTarget.Render(visual);
try
{
return new CroppedBitmap(renderTarget, new Int32Rect(0, 0, (int)actualWidth, (int)actualHeight));
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return null;
}
}
public static BitmapSource CaptureScreen(this UIElement visualElement, int? desiredLongestEdge = null)
{
double scale = 1;
if (desiredLongestEdge.HasValue)
{
if (visualElement.RenderSize.Width > visualElement.RenderSize.Height)
{
scale = desiredLongestEdge.Value/ visualElement.RenderSize.Width;
}
else
{
scale = desiredLongestEdge.Value / visualElement.RenderSize.Height ;
}
}
var targetBitmap =
new RenderTargetBitmap(
(int) Math.Ceiling(scale * (visualElement.RenderSize.Width + 1)),
(int) Math.Ceiling(scale * (visualElement.RenderSize.Height + 1)),
scale * 96,
scale * 96,
PixelFormats.Pbgra32);
visualElement.Measure(visualElement.RenderSize); //Important
visualElement.Arrange(new Rect(visualElement.RenderSize)); //Important
targetBitmap.Render(visualElement);
return targetBitmap;
}
How to save Geometry as image?
For example i have List<Geometry>.
I want it to be as follows:
for (int i = 0; i < GeometryList.Count; i++)
{
Pen TestPen = new Pen(Brushes.Black, 1);
GeometryDrawing TestDrawing = new GeometryDrawing(Brushes.Black, TestPen, TestGeometry);
Bitmap b = TestDrawing as Bitmap;
b.Save(System.AppDomain.CurrentDomain.BaseDirectory + i + ".png", ImageFormat.Png);
}
Update:
The code I wrote a few hours ago:
private void CreateFontMap(string PathTofont)
{
GlyphTypeface font = new GlyphTypeface(new Uri(PathTofont));
List<ushort> fontNum = new List<ushort>();
foreach (KeyValuePair<int, ushort> kvp in font.CharacterToGlyphMap)
{
fontNum.Add(kvp.Value);
}
if (fontNum.Count > 0)
{
int mapWidth = 50 * 20;
int mapHeight = 50 * (getRowNum(fontNum.Count + 1) + 1);
Bitmap b = new Bitmap(mapWidth, mapHeight);
Graphics g = Graphics.FromImage(b);
System.Windows.Media.Pen glyphPen = new System.Windows.Media.Pen(System.Windows.Media.Brushes.Red, 1);
Geometry glyphGeometry;
for (int i = 0; i < fontNum.Count; i++)
{
glyphGeometry = font.GetGlyphOutline(fontNum[i], 50, 1);
RenderTargetBitmap bmp = new RenderTargetBitmap(50, 50, 96, 96, PixelFormats.Pbgra32);
DrawingVisual viz = new DrawingVisual();
DrawingContext dc = viz.RenderOpen();
dc.DrawGeometry(System.Windows.Media.Brushes.Red, null, glyphGeometry);
dc.Close();
bmp.Render(viz);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bmp));
MemoryStream myStream = new MemoryStream();
encoder.Save(myStream);
int rowNum = (getRowNum(i));
g.DrawImage(System.Drawing.Bitmap.FromStream(myStream), new PointF((i - rowNum * 20) * 50, rowNum * 50));
}
g.Dispose();
b.Save(System.AppDomain.CurrentDomain.BaseDirectory + "map.png", ImageFormat.Png);
b.Dispose();
}
}
private int getRowNum(int p)
{
return p / 20;
}
But instead of , I get Img2.
Update 2:
I changed this:
DrawingVisual viz = new DrawingVisual();
DrawingContext dc = viz.RenderOpen();
dc.DrawGeometry(System.Windows.Media.Brushes.Red, null, glyphGeometry);
dc.Close();
to:
DrawingVisual viz = new DrawingVisual();
DrawingContext dc = viz.RenderOpen();
dc.DrawImage(geometryImage, new Rect(0, 0, 50, 50));
dc.Close();
and added:
glyphDrawing = new GeometryDrawing(System.Windows.Media.Brushes.Black, glyphPen, glyphGeometry);
DrawingImage geometryImage = new DrawingImage(glyphDrawing);
geometryImage.Freeze();
img1.Source = geometryImage;
And all working.
For anyone who is wanting to render geometry centered in a fixed size, this is the code to do it:
const int TargetSize = 14;
private static void Save(Geometry geometry, string fileName)
{
var rect = geometry.GetRenderBounds(new Pen(Brushes.Black, 0));
var bigger = rect.Width > rect.Height ? rect.Width : rect.Height;
var scale = TargetSize / bigger;
Geometry scaledGeometry = Geometry.Combine(geometry, geometry, GeometryCombineMode.Intersect, new ScaleTransform(scale, scale));
rect = scaledGeometry.GetRenderBounds(new Pen(Brushes.Black, 0));
Geometry transformedGeometry = Geometry.Combine(scaledGeometry, scaledGeometry, GeometryCombineMode.Intersect, new TranslateTransform(((TargetSize - rect.Width) / 2) - rect.Left, ((TargetSize - rect.Height) / 2) - rect.Top));
RenderTargetBitmap bmp = new RenderTargetBitmap(TargetSize, TargetSize, // Size
96, 96, // DPI
PixelFormats.Pbgra32);
DrawingVisual viz = new DrawingVisual();
using (DrawingContext dc = viz.RenderOpen())
{
dc.DrawGeometry(Brushes.Black, null, transformedGeometry);
}
bmp.Render(viz);
PngBitmapEncoder pngEncoder = new PngBitmapEncoder();
pngEncoder.Frames.Add(BitmapFrame.Create(bmp));
using (FileStream file = new FileStream(fileName, FileMode.Create))
pngEncoder.Save(file);
}
// Create the bitmap we'll render to
RenderTargetBitmap bmp =
new RenderTargetBitmap(100, 100, // Size
96, 96, // DPI
PixelFormats.Pbgra32);
// Create a list of random circle geometries
List<Geometry> geoList = new List<Geometry>();
Random rand = new Random();
for (int i=0; i<10; i++)
{
double radius = rand.Next(5, 10);
Point center = new Point(rand.Next(25, 75), rand.Next(25,75));
geoList.Add(new EllipseGeometry(center, radius, radius));
}
// The light-weight visual element that will draw the geometries
DrawingVisual viz = new DrawingVisual();
using (DrawingContext dc = viz.RenderOpen())
{ // The DC lets us draw to the DrawingVisual directly
foreach (var g in geoList)
dc.DrawGeometry(Brushes.Red, null, g);
} // the DC is closed as it falls out of the using statement
// draw the visual on the bitmap
bmp.Render(viz);
// instantiate an encoder to save the file
PngBitmapEncoder pngEncoder = new PngBitmapEncoder();
// add this bitmap to the encoders set of frames
pngEncoder.Frames.Add(BitmapFrame.Create(bmp));
// save the bitmap as an .png file
using (FileStream file = new FileStream("Spots.png", FileMode.Create))
pngEncoder.Save(file);
Based on your comments to the section above, it looks like you're trying to create a table of glyphs for a font and save it out to an image file. Here's how you accomplish this:
// I'm generating the glyphs differently for testing.
// I tested with fontName="Arial"
Typeface face = new Typeface(fontName);
GlyphTypeface font;
if (!face.TryGetGlyphTypeface(out font))
return; // bail if something goes wrong
int ColumnCount = 10;
int MaxDrawCount = 30; // use int.MaxValue to draw them all
double fontSize = 50d;
// the height of each cell has to include over/underhanging glyphs
Size cellSize = new Size(fontSize, fontSize * font.Height);
var Glyphs = from glyphIndex in font.CharacterToGlyphMap.Values
select font.GetGlyphOutline(glyphIndex, fontSize, 1d);
// now create the visual we'll draw them to
DrawingVisual viz = new DrawingVisual();
int drawCount = -1;
using (DrawingContext dc = viz.RenderOpen())
{
foreach (var g in Glyphs)
{
drawCount++;
if (drawCount >= MaxDrawCount)
break; // don't draw more than you want
if (g.IsEmpty()) continue; // don't draw the blank ones
// center horizontally in the cell
double xOffset = (drawCount % ColumnCount) * cellSize.Width + cellSize.Width / 2d - g.Bounds.Width / 2d;
// place the character on the baseline of the cell
double yOffset = (drawCount / ColumnCount) * cellSize.Height + fontSize * font.Baseline;
dc.PushTransform(new TranslateTransform(xOffset, yOffset));
dc.DrawGeometry(Brushes.Red, null, g);
dc.Pop(); // get rid of the transform
}
}
int RowCount = drawCount / ColumnCount;
if (drawCount % ColumnCount != 0)
RowCount++; // to include partial rows
int bitWidth = (int)Math.Ceiling(cellSize.Width * ColumnCount);
int bitHeight = (int)Math.Ceiling(cellSize.Height * RowCount);
RenderTargetBitmap bmp = new RenderTargetBitmap(
bitWidth, bitHeight,
96, 96,
PixelFormats.Pbgra32);
bmp.Render(viz);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bmp));
using (FileStream file = new FileStream("FontTable.png", FileMode.Create))
encoder.Save(file);
I use this:
private void GenerateFontMap(string PathTofont, int GlyphsPerRow, int WidthAndHeight)
{
GlyphTypeface font = new GlyphTypeface(new Uri(PathTofont));
List<ushort> fontNum = new List<ushort>();
foreach (KeyValuePair<int, ushort> kvp in font.CharacterToGlyphMap)
{
fontNum.Add(kvp.Value);
}
if (fontNum.Count > 0)
{
int mapWidth = WidthAndHeight * GlyphsPerRow;
int mapHeight = WidthAndHeight * ((fontNum.Count + 1) / GlyphsPerRow + 1);
Bitmap b = new Bitmap(mapWidth, mapHeight);
Graphics g = Graphics.FromImage(b);
System.Windows.Media.Pen glyphPen = new System.Windows.Media.Pen(System.Windows.Media.Brushes.Black, 1);
Geometry glyphGeometry;
GeometryDrawing glyphDrawing;
PngBitmapEncoder encoder;
RenderTargetBitmap bmp;
DrawingVisual viz;
for (int i = 0; i < fontNum.Count; i++)
{
glyphGeometry = font.GetGlyphOutline(fontNum[i], WidthAndHeight, 1);
glyphDrawing = new GeometryDrawing(System.Windows.Media.Brushes.Black, glyphPen, glyphGeometry);
DrawingImage geometryImage = new DrawingImage(glyphDrawing);
geometryImage.Freeze();
viz = new DrawingVisual();
DrawingContext dc = viz.RenderOpen();
dc.DrawImage(geometryImage, new Rect(0, 0, geometryImage.Width, geometryImage.Height));
dc.Close();
bmp = new RenderTargetBitmap(WidthAndHeight, WidthAndHeight, 96, 96, PixelFormats.Pbgra32);
bmp.Render(viz);
encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bmp));
MemoryStream myStream = new MemoryStream();
encoder.Save(myStream);
g.DrawImage(System.Drawing.Bitmap.FromStream(myStream), new PointF((i - (i / GlyphsPerRow) * GlyphsPerRow) * WidthAndHeight, i / GlyphsPerRow * WidthAndHeight));
}
g.Dispose();
b.Save("map.png", ImageFormat.Png);
b.Dispose();
}
}