Use a different image depending on the computer's dpi setting - wpf

I have used 16x16 px images in my application, so that I get crisp edges and no automatic resizing at the standard dpi setting of 96.
When the user changes their dpi setting, the images get enlarged, and since the source files are only 16x16, they look naturally bad. Is there a way I can provide multiple images for a particular image source, and the best one will be chosen automatically? For example I provide images with the size of 16x16, 20x20 and 24x24 pixels, when the image's size is 16x16 [wpf units], so I have one perfect match for 96, 120 and 144 dpi?

What best i can think is to set the image source dynamically at run-time based on system's DPI settings. In code-behind you can set dynamically like -
ImageViewer1.Source = new BitmapImage(new Uri(#"\\myserver\\folder1\\sample.png"));
Listen to this event in your class to get notified about the dpi settings changed of computer - Microsoft.Win32.SystemEvents.DisplaySettingsChanged. Details of it can be found here - System Events
Also, you can get the system dpi value using the following code -
float dpiX, dpiY;
Graphics graphics = this.CreateGraphics();
dpiX = graphics.DpiX;
dpiY = graphics.DpiY;
Move this logic to a property and based on the property value, dynamically set the image source.

Related

WPF RenderTargetBitmap Missing Elements

I have a TreeView with small icons displayed in the data template. I'm trying to save the Treeview as a PNG using RenderTargetBitmap.
The image saves correctly on small data sets. However, if the data set becomes too large, some of the icons are excluded from the final image. The magic number seems to be 200 items. It doesn't seem to matter if the tree is deep or wide, after 200 items, the icons are not rendered.
Added Code
So here is my code that I'm using to create an image.
RenderTargetBitmap targetBitmap = new RenderTargetBitmap(
(int)_treeView.ActualWidth,
(int)_treeView.ActualHeight,
96, 96, PixelFormats.Default);
targetBitmap.Render(_treeView);
Added Screen Shot
Notice the missing icons way over on the right side of the tree.
Now if I collapse a few branches, thus hiding some of the other icons, then these icons are included. It's almost like RenderTargetBitmap.Render doesn't have the power to render all of the icons. Or it may have something to do with virtual panels.
Here is a closer look.
What I immediately noticed that you have HUGE image. Width 12000. I am surprised that you even got that close.
As MSDN states, the texture width/height are limited by DirectX texture limits.
The maximum rendered size of a XAML visual tree is restricted by the maximum dimensions of a Microsoft DirectX texture; for more info see Resource Limits (Direct3D). This limit can vary depending on the hardware whre the app runs. Very large content that exceeds this limit might be scaled to fit. If scaling limits are applied in this way, the rendered size after scaling can be queried using the PixelWidth and PixelHeight properties. For example, a 10000 by 10000 pixel XAML visual tree might be scaled to 4096 by 4096 pixels, an example of a particular limit as forced by the hardware where the app runs.
http://msdn.microsoft.com/library/windows/apps/dn298548
I suspect these things:
Virtualization cutting off some things - I've had the exact problem in past with DataGrid, and the problem was virtualization. Your case doesn't seem like one though.
Too big texture can cause undefined behaviour.
You can try disabling hardware acceleration. The thing causes quite few hardcore bugs. http://msdn.microsoft.com/en-us/library/system.windows.media.renderoptions.processrendermode.aspx
Other than that - it will be tricky, but I am pretty sure that it will work beautifully:
1) start with the root object, and traverse the root object childrens recursively, until you find an object that is less than 1000 x 1000. Take picture of it using RenderTargetBitmap(BMP) and merge it to IN-MEMORY-BMP. Do it for each children.
You should be able to calculate all this stuff.
For the records: there's a workaround.
Instead of rendering your Visual directly with RenderTargetBitmap, use an interim DrawingVisual. Paint your Visual into the DrawingVisual using a VisualBrush and then use RenderTargetBitmap with the DrawingVisual.
Like this:
public BitmapSource RenderVisualToBitmap(Visual visual)
{
var contentBounds = VisualTreeHelper.GetContentBounds(visual);
var drawingVisual = new DrawingVisual();
using (var drawingContext = drawingVisual.RenderOpen())
{
var visualBrush = new VisualBrush(visual);
drawingContext.DrawRectangle(visualBrush, null, contentBounds);
}
var renderTargetBitmap = new RenderTargetBitmap((int)contentBounds.Width, (int)contentBounds.Height, 96, 96, PixelFormats.Default);
renderTargetBitmap.Render(drawingVisual);
return renderTargetBitmap;
}
Note however that as your VisualBrush gets bigger the resulting image gets more and more fuzzy (when rendering with high DPI). To work around this problem use a series of smaller VisualBrush "tiles" as described here:
https://srndolha.wordpress.com/2012/10/16/exported-drawingvisual-quality-when-using-visualbrush/

Printing with more than 96 dpi in WPF

Let me explain the problem, I'm getting stuck in it
If I change the dpi settings from the dialog of Printing Preferences of a virtual printer like PDF Creator or any printer that allows to change this setting, and then set a breakpoint like the code below:
PrintDialog printDialog = new PrintDialog();
if ((bool)printDialog.ShowDialog().GetValueOrDefault())
{
System.Printing.PrintCapabilities capabilities = printDialog.PrintQueue.GetPrintCapabilities(printDialog.PrintTicket);
...... insert breakpoint here
}
I can see that the properties printDialog.PrintTicket.PageResolution.X; and printDialog.PrintTicket.PageResolution.Y change correctly while printDialog.PrintTicket.PageMediaSize.Width and printDialog.PrintTicket.PageMediaSize.Height don't change despite the printer resolution change... an A4 paper in portrait mode will always have PageMediaSize.Height = 1122.5196850393702 and PageMediaSize.Width = 793.70078740157476 no matter which resolution is set before ..... for WPF the unit size of these dimensions is set to 1/96th inch but when is Ok on screen because default screen resolution is 96 dpi on the other side is wrong on the printer because it has a different resolution, in other words confuting that Height and Width of the paper are read only properties if I cannot find the way to tell WPF that the unit size of the printer is not 1/96th inch but for example 1/300th inch (if on the printer I previously set 300 dpi ) there's absolutely no way to print at higher resolution than 96dpi
A last note, in my specific case I cannot use RenderTargetBitmap and then resize all to match printer's paper height and width settings because I'm printing high definition barcode images and it would cause an image rescaling that would make the barcode unreadable on final paper because i create it with the purpose to be printed with a resolution of 300dpi which without a resizing will result out of bounds because WPF is telling me the printer paper dimensions in the wrong unit size (1/96th inch) despite the real dpis prevoiusly set on printer
Hoping to have clarified enough the problem,
thanks in advance,
Dave

How to prevent WPF from scaling with the Windows font size options?

I don't want my WPF GUI to scale with Windows font size options (DPI). It's not just a matter of specifying a fixed font size on the UserControl because scaling affects images and borders in the UserControl. Is there a property that I can set on the UserControl to turn off scalling? If not, how can I do that?
You could scale down your UserControl based on the current DPI setting. For example, if you wrapped your UserControl with the following DpiDecorator, then it should look the same regardless of the DPI setting:
public class DpiDecorator : Decorator {
public DpiDecorator() {
this.Loaded += (s, e) => {
Matrix m = PresentationSource.FromVisual(this).CompositionTarget.TransformToDevice;
ScaleTransform dpiTransform = new ScaleTransform(1 / m.M11, 1 / m.M22);
if (dpiTransform.CanFreeze)
dpiTransform.Freeze();
this.LayoutTransform = dpiTransform;
};
}
}
Or you could move that logic to your UserControl.
The code to obtain the DPI scale factor was from this blog post.
Windows includes a compatibility helper for buggy applications that fail under high-dpi settings:
Be sure to leave off (or turn off) "Use Windows XP style DPI scaling":
And be sure your application does not have a "dpiAware" entry in its assembly manifest.
Windows will lie to your application, tell it that it's 96dpi, and then the graphics card will scale the entire window up for you.
Everything will be slightly fuzzy, and generally unpleasant to use, but it will work well enough until you can fix the buggy WinForm control.
Note: The dpiAware manifest entry lets your application tell Windows that it wants to opt-out of the dpi-scaling. Applications only add this item if they've been tested at high-dpi.
in addition the the DpiDecorator, you will also need to fix the font size of your items. For example, in Windows XP, if your setting is set to Large Fonts, Menu Item font size is set to 14 and is also scaled up using the DPI setting, so if you don't fix your MenuItem font size or any other UI item font, you will get Window's default value for this item type. Also remember that a user can manually change the font size/font face for other items...

Maintain Aspect Ratio in a Silverlight Image

Ok .. so here's the scenario. I've got a WP7 silverlight app, that loads an image from the net. Now, these images will be taken from mobile devices, so they may be in portrait or landscape mode. Certainly not a square.
Is there any way to maintain the aspect ratio when I show these in a silverlight <Image> control?
I'm ok with either of two resolutions:
That the image shows up in its correct aspect ratio within a predefined box that I've defined in xaml
Or that the image is cropped into the square
The way silverlight was built, you can set the width OR the height on the image, it will automatically max out whatever property you set and calculates the other side of the image so that it keeps the aspect-ratio.
So, just set a width on the image and center or right,left,top,bottom align it. (do not stretch it).

How to use PixelFormats.IndexedX with RenderTargetBitmap?

I am looking to render a DrawingVisual (visual in the example) to a bitmap using RenderTargetBitmap with the view to set this bitmap as the background to a Canvas as below:
var bmp = new RenderTargetBitmap(2000, 50, 120, 96, PixelFormats.Indexed2);
bmp.Render(visual);
var brush = new ImageBrush(bmp) { Stretch = Stretch.Fill };
Canvas.Background = brush;
When using PixelFormats.Default as the last argument to RenderTargetBitmap, the image renders as expected. However, when I choose PixelFormats.Indexed2 (or any of the PixelFormats.IndexedX), my code seems to exit the method without an exception, the bmp.Render line is never called and hence the image is not displayed on the Canvas.
How to use the IndexedX pixel formats with RenderTargetBitmap? Or are there other ways to reduce the memory footprint of the image? It only uses three colors, so using a palette rather than 32bit RGB seemed the way to go.
You can't. RenderTargetBitmap only supports the Pbgra32 pixel format. That's because WPF's rendering system works entirely in 32 bits per pixel. That's the format in which it generates images, and it's also the format in which it prefers images to be in if you want to render them. (If you provide it with a bitmap in any other format, it'll need to convert it into a 32 bit per pixel representation first.)
What are you planning to do with this bitmap? If you want to render it in a WPF application, it'll need to be converted to a 32bpp format first in any case, so you risk using more memory if you attempt to hold it internally in any other format. (You'll have your supposedly memory-efficient representation and the version WPF's actually able to work with.) Not to mention the extra CPU time spent converting between your chosen format and a format WPF can work with.

Resources