WPF FormattedText randomly disappears in high resolution images - wpf

I have the requirement to create 600 DPI "trifold" images, which have the dimensions of 25.5"x11" (three times the size of a Letter page). To do so, I’m using WPF Imaging through the DrawingVisual, DrawingContext, and RenderTargetBitmap classes.
When I generate the image in lower resolutions, 400 DPI or less for example, all of the text displays in the correct positions as expected. However, once I increase my image resolution to the 500 DPI level and beyond, certain text positioned in the far right of the image will simply disappear, while other relatively positioned text/shapes print perfectly. The craziest part about it is that as I try varying DPIs, different text will appear/disappear. In one test case, 600 DPI is missing one set of drawn FormattedTexts, 650 DPI is missing a different set of drawn FormattedTexts, and 700 DPI prints everything fine!
I’ve recreated the issue with the snippet of code below. Run as-is (600 DPI) and all you get is a very large white image. Change the Dpi constant to 400 or lower, and it prints the text just fine.
Note that I’ve tried turning many of the knobs within the DrawingVisual class (e.g. VisualBitmapScalingMode, VisualTextRenderingMode, VisualEdgeMode, etc.) to no avail. Most of my research on those settings found them as useful settings to correct “fuzzy” text, not disappearing text. I’ve also had no luck with any of the guideline/snap settings of the DrawingVisual or DrawingContext.
Note that I’ve recreated this issue on both Win7 and Win2008R2, and my application is running .NET 4.5.
Any ideas?
const double ImageWidthInches = 25.5;
const double ImageHeightInches = 11.0;
const double Dpi = 600.0;
const double DeviceIndependentUnits = 96.0;
const double TypographicUnits = 72.0;
var visual = new DrawingVisual();
var drawing = visual.RenderOpen();
drawing.DrawRectangle(
Brushes.White,
null,
new Rect(0,
0,
ImageWidthInches*DeviceIndependentUnits,
ImageHeightInches*DeviceIndependentUnits));
var formattedText = new FormattedText(
"Why doesn't this display?",
CultureInfo.CurrentUICulture,
FlowDirection.LeftToRight,
new Typeface(new FontFamily("Arial Narrow"),
FontStyles.Normal,
FontWeights.Normal,
FontStretches.Normal),
8.0*DeviceIndependentUnits/TypographicUnits,
Brushes.Black);
drawing.DrawText(formattedText,
new Point(23.39*DeviceIndependentUnits,
2.6635416666666671*DeviceIndependentUnits));
drawing.Close();
var renderTarget = new RenderTargetBitmap(
(int) (ImageWidthInches*Dpi),
(int) (ImageHeightInches*Dpi),
Dpi,
Dpi,
PixelFormats.Default);
renderTarget.Render(visual);
var tiffEncoder = new TiffBitmapEncoder {Compression = TiffCompressOption.Ccitt4};
tiffEncoder.Frames.Add(BitmapFrame.Create(renderTarget));
using (var fileStream = new FileStream(#"c:\recreateWpfTextIssue.tif", FileMode.Create, FileAccess.Write))
tiffEncoder.Save(fileStream);

The workaround to this bug is to round the font size to 2 decimal positions:
Math.Round(8.0*DeviceIndependentUnits/TypographicUnits, 2),
This and some extra information can be found in the matching MSDN post: http://social.msdn.microsoft.com/Forums/en-US/98717e53-89f7-4d5f-823b-7184781a7b85/wpf-formattedtext-randomly-disappears-in-high-resolution-images

Related

WinForm Simple Raw Bitmap Font Writing?

I need to write in a form using a bitmap font at a specific point size, without having Windows do any antialiasing or otherwise "helping" the display of the text. The reason is that the text will be saved out as a bitmap for display on a low-resolution display (eg a Netduino-driven bitmap with space for 120 pixels wide and 40 pixels high) , so if I want a black "A" on the screen I can't have grey pixels added in and arund the letters.
I need to use a font like this
http://robey.lag.net/2010/01/23/tiny-monospace-font.html
Although I know Windows doesn't do BDF I included that as a reference to the kind of no-nonsense super small typeface that I need to use in Windows.
Using C#, Franework 4.5.2, what can I do to make .NET emit a typeface as a pure unscaled bitmap?
I built a bitmap using the "Tiny" TrueType font at 6 point. Notice in the generated bitmap image that the text is all not pure white, although I specified it that way.
using (var gb = Graphics.FromImage(mybitmap))
{
gb.PixelOffsetMode = PixelOffsetMode.Half;
gb.SmoothingMode = SmoothingMode.None;
gb.InterpolationMode = InterpolationMode.NearestNeighbor;
gb.TextRenderingHint = System.Drawing.Text.TextRenderingHint.SingleBitPerPixelGridFit;
gb.TextContrast = 0;
gb.Clear(colorbg);
var fontSize = Convert.ToSingle(6);
var nowFont = new Font( myfont , fontSize, GraphicsUnit.Pixel );
TextRenderer.DrawText(gb, "pack my box..." , nowFont , new Point(0, 0), colorforeground);
}
Thanks.
Set the Graphics.TextRenderingHint property to TextRenderingHint.SingleBitPerPixelGridFit. If the font has no TrueType hinting at all then SingleBitPerPixel is probably your preferred choice. Either renders text without any anti-aliasing.

Drawing to a bitmap from a WPF canvas

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.

WPF Bitmap performance

I'm trying to understand why my images are not snappy, so I built a sample to test WPF performance. I used a timer to calculate how long my "display images" event handler executed, and used a stop watch to measure how long it took the images to appear on the screen. The bottom line: when displaying 100, 1600, 2500 and 3600 images, WPF took 2, 9, 12 and 16 seconds after my code had finished to display the images on the screen. So I feel helpless: It seems I can't improve my code to make the images appear faster - I need to do something with WPF!
So my question is: What do I need to do differently to make the images display faster?
The test setup is simple:
The window contains a Grid. After the "test" button is clicked, row and column definitions are added.Then an Image is added to each cell of the grid as follows:
var image = new Image();
image.BeginInit();
image.Name = ImageNameFromCell(theRow, theColumn);
image.Stretch = Stretch.None;
image.HorizontalAlignment = HorizontalAlignment.Center;
image.VerticalAlignment = VerticalAlignment.Center;
RenderOptions.SetBitmapScalingMode(image, BitmapScalingMode.LowQuality);
image.EndInit();
theGrid.Children.Add(image);
Finally, the Source of each image is set to a bitmap:a gray-scale image already scaled down to the estimated screen size. The bitmap is generated as follows:
var smallerBitmapImage = new BitmapImage();
smallerBitmapImage.BeginInit();
smallerBitmapImage.DecodePixelWidth = (int)(theImageWidth);
smallerBitmapImage.UriSource = theUri;
smallerBitmapImage.CacheOption = BitmapCacheOption.None;
smallerBitmapImage.EndInit();
//BitmapFrame bitmapFrame = BitmapFrame.Create(this.FullPath);
var convertedBitmap = new FormatConvertedBitmap();
convertedBitmap.BeginInit();
convertedBitmap.Source = smallerBitmapImage;
convertedBitmap.DestinationFormat = PixelFormats.Gray16;
convertedBitmap.EndInit();
convertedBitmap.Freeze();
So, I'm at my wits end. The images appear with a noticeable delay, and it seems to be out of my control. What can I do?
What appears to have made the difference is setting the image's cache option to OnLoad
smallerBitmapImage.CacheOption = BitmapCacheOption.OnLoad;
This moved the work to my event handler, so now I can use pre-fetching to do this at the background.
Do you actually see all those images at the same time? If not you can use some ItemsControl with a virtualizing panel so only images in view are displayed. (Speaking of panels, your current setup could also be replaced with an ItemsControl which uses a UniformGrid as panel)
You could also try to write a better decoder, which probably is a wasted effort.

How do I set the background colour of a transparent PNG (WPF)?

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.

WPF Background setting failed

Here is a code.
var image = new BitmapImage(new Uri(#"pack://application:,,,/Images/background.png",
UriKind.RelativeOrAbsolute));
var backgroundBrush = new ImageBrush()
{
ImageSource = image,
Viewport = new Rect(0, 0, image.PixelWidth / ActualWidth,
image.PixelHeight / ActualHeight),
TileMode = TileMode.Tile,
Stretch = Stretch.None,
};
// Set it for the main window.
Background = backgroundBrush;
It works just fine on my PC with XPSP3 and .Net 4.0. But when I run the same sample on Eee PC T91MT with Windows 7 Home Premium it fails. No exceptions, but nothing is drawn (solid color brushes ARE drawn if used instead, though). I thought it could be the result of limited resources, but on Viliv S5, that has about the same specs it works fine too.
Any ideas?
Thanks!
UPDATE
The root of the problem is Viewport's rect. Since the bitmap has twice window's size by X, the rect is (0, 0, 2, 1). So, on power computer with XPSP3, the left half of the image is drawn. But on Eee PC it causes a problem with visualization.
The answer is just normalizing Viewport rectangle. E.g. instead of (0,0,2,1) I had to set it as (0,0,1,0.5).
I'm not sure, but it looks like WPF just transmit rect values (after some transformation) into a D3D driver, which is (or is not) able to handle it the right way. So, non-normalized rect Viewport works on GeForce-based machine but does not on Eee PC with it's integrated video's driver.

Resources