How to display a Bitmap Windows zoom level (DPI?) independent in WPF - wpf

I am new to WPF, and facing to the following issue.
I am displaying a bitmap, xaml:
<ControlTemplate x:Key="MyEditorImageTemplate">
<Image VerticalAlignment="Top" Margin="3,1,0,0"
RenderOptions.BitmapScalingMode="NearestNeighbor"
Stretch="None">
<Image.Source>
<MultiBinding Converter="{StaticResource MyEditorConverter}">
<Binding Path="....." />
The Bitmap is coming from BitmapSource, provided the following way:
Bitmap^ bitmap = System::Drawing::Image::FromHbitmap((IntPtr)aBmp);
IntPtr hbmp = bitmap->GetHbitmap();
BitmapSource bs =
Imaging::CreateBitmapSourceFromHBitmap(hbmp,
System::IntPtr::Zero,
Int32Rect(0,0,nWidth,nHeight),
BitmapSizeOptions::FromEmptyOptions());
Now this works fine, until I have the windows zoom level at 100%.
If I change it in Windows to 125%:
The bitmap is also zoomed on the GUI.
I understand that this is -sort of- the expected, but is there any way that I still show it 1-1 pixel size, ignoring windows zoom settings?

Ok, I found the solution.
You just need an own Image class, derived from Image, and simple override the OnRender, where you take care of the proper resizing (if necessary).
protected override void OnRender(DrawingContext dc)
{
//base.OnRender(dc); --> we do out own painting
Rect targetRect = new Rect(RenderSize);
PresentationSource ps = PresentationSource.FromVisual(this);
if (ps != null && Source != null)
{
Matrix fromDevice = ps.CompositionTarget.TransformFromDevice;
Vector currentVisibleSize = new Vector(Source.Width, Source.Height);
Vector originalRestoredSize = fromDevice.Transform(currentVisibleSize);
targetRect = new Rect(new Size(originalRestoredSize.X, originalRestoredSize.Y));
}
dc.DrawImage(Source, targetRect);
}
With this, regardless of the Windows zoom setting, you will have always the 100% original size.
I got the idea from this site: http://blogs.msdn.com/b/dwayneneed/archive/2007/10/05/blurry-bitmaps.aspx

Related

WPF Image Uniform to None

I want to make my Image to stretch uniform when its size is smaller than original. If there is enough space, I don't want to stretch the image. I'am using the Image control:
<Image Name="ImageBox" Stretch="Uniform" SizeChanged="ImageBox_SizeChanged_1"/>
My event handler:
private void ImageBox_SizeChanged_1(object sender, SizeChangedEventArgs e)
{
if (OriginalImageSize == null) return;
if (ImageBox.RenderSize.Width > OriginalImageSize.Width && ImageBox.RenderSize.Height > OriginalImageSize.Height)
ImageBox.Stretch = Stretch.None;
else
ImageBox.Stretch = Stretch.Uniform;
}
When I load image it's ok, next I resize the window so there is enough space to show whole image and scaling is not necessary, image starts blinking (from Stretch.None to Stretch.Uniform I guess).
Any advice?
I think you are looking for the StretchDirection property. You might want to try
<Image Name="ImageBox" Stretch="Uniform" StretchDirection="DownOnly"/>
without your event.

WPF, C#: Draw a line onto existing bitmap in image control

I have a bitmap image in an image control
I need to draw a red line on the bitmap each time I click with the mouse onto it, at the place where I clicked the mouse.
I first thought of creating a Line object, but found out, that I cannot add the Line. I would need a canvas. But if I put my image in a canvas, my bitmap does not stretch over the whole canvas (I found out, that the coordinates of the bitmap determine the place on the canvas, so my bitmap is wrongly displayed.)
Then I tried using graphics
Graphics graphics = Graphics.FromImage(bitmapImg);
graphics.DrawLine(new System.Drawing.Pen(System.Drawing.Color.Red), 0, 0, bitmapImg.Width, bitmapImg.Height); //not the line yet, just for testing
graphics.DrawImage(bitmapImg, 0, 0, bitmapImg.Width,bitmapImg.Height);
graphics.Dispose();
However, I don`t get anything painted onto my bitmap........
Now I think, I probably have to get the bitmap into an array and then change the pixel color to get the line in the bitmap. I believe, that this would be very slow.
I am now trying something with visualDrawing, however, I have not got it to work yet:-(
What is a good way to get a line onto an existing bitmap in WPF C#???? and how to remove it?
I would be glad for any help! Thank you! I posted it already on the MS forum page, but no answer so far.
When you do Graphics.FromImage, this Graphics class (and also the System.Drawing.Pen) do not belong to WPF, they are part from WinForms and they are internally using Windows' GDI+ calls to draw and cannot draw on top of WPF.
If you didn't got an error when compiling the first line of your code, then probably your bitmapImg is a System.Drawing.Image (from WinForms) not an Image control from WPF (System.Window.Controls.Image).
As adrianm mentioned, the easiest way will probably be to use a Grid:
<Grid>
<Image Source="your image" />
<Line Name="line" Visibility="Hidden" Stroke="Red" StrokeThickness="1" />
</Grid>
Then, in your click event handler you can make the line visible and give it the coordinates you want:
line.Visibility = Visible;
line.X1 = mouse_x;
line.Y1 = mouse_y;
line.X2 = ...;
line.Y2 = ...;
You can place a canvas with the background as transparent on top of your BitmapImage and then draw the line as required.
Code from xaml file:
<Grid>
<Image Source="C:\Users\sm143444\Desktop\download.jpg" />
<Canvas Background="Transparent" x:Name="draw" />
</Grid>
Code from Xaml.cs:
public MainWindow()
{
InitializeComponent();
Point startPoint = new Point(50, 50);
Line newLine = new Line();
newLine.Stroke = Brushes.Black;
newLine.Fill = Brushes.Black;
newLine.StrokeLineJoin = PenLineJoin.Bevel;
newLine.X1 = startPoint.X;
newLine.Y1 = startPoint.Y;
newLine.X2 = startPoint.X + 100;
newLine.Y2 = startPoint.Y + 100;
newLine.StrokeThickness = 2;
this.draw.Children.Add(newLine);
}
output
Or you can even add a ZIndex to your image and Line so that they are laid on different layers on canvas.

RenderTargetBitmap + Resource'd VisualBrush = incomplete image

I've found a new twist on the "Visual to RenderTargetBitmap" question!
I'm rendering previews of WPF stuff for a designer. That means I need to take a WPF visual and render it to a bitmap without that visual ever being displayed. Got a nice little method to do it like to see it here it goes
private static BitmapSource CreateBitmapSource(FrameworkElement visual)
{
Border b = new Border { Width = visual.Width, Height = visual.Height };
b.BorderBrush = Brushes.Black;
b.BorderThickness = new Thickness(1);
b.Background = Brushes.White;
b.Child = visual;
b.Measure(new Size(b.Width, b.Height));
b.Arrange(new Rect(b.DesiredSize));
RenderTargetBitmap rtb = new RenderTargetBitmap(
(int)b.ActualWidth,
(int)b.ActualHeight,
96,
96,
PixelFormats.Pbgra32);
// intermediate step here to ensure any VisualBrushes are rendered properly
DrawingVisual dv = new DrawingVisual();
using (var dc = dv.RenderOpen())
{
var vb = new VisualBrush(b);
dc.DrawRectangle(vb, null, new Rect(new Point(), b.DesiredSize));
}
rtb.Render(dv);
return rtb;
}
Works fine, except for one leeetle thing... if my FrameworkElement has a VisualBrush, that brush doesn't end up in the final rendered bitmap. Something like this:
<UserControl.Resources>
<VisualBrush
x:Key="LOLgo">
<VisualBrush.Visual>
<!-- blah blah -->
<Grid
Background="{StaticResource LOLgo}">
<!-- yadda yadda -->
Everything else renders to the bitmap, but that VisualBrush just won't show. The obvious google solutions have been attempted and have failed. Even the ones that specifically mention VisualBrushes missing from RTB'd bitmaps.
I have a sneaky suspicion this might be caused by the fact that its a Resource, and that lazy resource isn't being inlined. So a possible fix would be to, somehow(???), force resolution of all static resource references before rendering. But I have absolutely no idea how to do that.
Anybody have a fix for this?
You have two problems:
You didn't set a PresentationSource on your visual so Loaded events won't fire.
You didn't flush the Dispatcher queue. Without flushing the Dispatcher queue, any functionality that uses Dispatcher callbacks won't work.
The immediate cause of your problem is failure to flush the Dispatcher queue, since VisualBrush uses it, but you will probably run into the PresentationSource problem before long so I would fix both of these.
Here is how I do it:
// Create the container
var container = new Border
{
Child = contentVisual,
Background = Brushes.White,
BorderBrush = Brushes.Black,
BorderThickness = new Thickness(1),
};
// Measure and arrange the container
container.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
container.Arrange(new Rect(container.DesiredSize));
// Temporarily add a PresentationSource if none exists
using(var temporaryPresentationSource = new HwndSource(new HwndSourceParameters()) { RootVisual = (VisualTreeHelper.GetParent(container)==null ? container : null) })
{
// Flush the dispatcher queue
Dispatcher.Invoke(DispatcherPriority.SystemIdle, new Action(() => { }));
// Render to bitmap
var rtb = new RenderTargetBitmap((int)b.ActualWidth, (int)b.ActualHeight, 96, 96, PixelFormats.Pbgra32);
rtb.Render(container);
return rtb;
}
FYI, StaticResource lookup is never delayed under any circumstances: It is processed the moment the XAML is loaded and immediately replaced with the value retrieved from the ResourceDictionary. The only way StaticResource could possibly be related is if it picked up the wrong resource because two resources had the same key. I just thought I should explain this -- it has nothing to do with your actual problem.
Well to inline it, you could just do something like this:
<Grid>
<Grid.Background>
<VisualBrush>
<VisualBrush.Visual>
<!-- blah blah -->
</VisualBrush.Visual>
</VisualBrush>
</Grid.Background>
</Grid>
If that doesn't work, my guess would be that it must be something specific with the Visual instance you are using (and that will require further code to better diagnose).

Image within a WPF application displays smaller than when viewed in external viewer

When I display a JPEG in my WPF application (using the following code), it is shown significantly smaller than if I open the JPEG in the Windows Picture Viewer at actual size.
I've drilled into the properties of my ImageSource at runtime and my image has:
a DPI of 219
a Height of 238.02739726027397
a Width of 312.54794520547944
a PixelHeight of 543
and a PixelWidth of 713
When I use a screen ruler to measure the WPF display of the image, I get approx. 313x240 pixels (which if I could positiont the ruler perfectly would probably be equal to the Width and Height that the ImageSource is reporting.).
My gut tells me this has something to do with WPF's use of device independent units (instead of pixels) but I can't make sense of it, and I still need to know how to display the image at the 'actual' size of 543x713 in my application.
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<StackPanel>
<Image Source="Image15.jpg" Stretch="None" />
</StackPanel>
</Window>
Thanks Mark! I did some Googling based on your info and found this article that provided a solution to get the result I wanted. This is starting to make sense now...
Edit: Linkrot. Pasting the critical text from the article here for reference....
<Image Source=”{Binding …}”
Stretch=”Uniform”
Width=”{Binding Source.PixelWidth,RelativeSource={RelativeSource Self}}”
Height=”{Binding Source.PixelHeight,RelativeSource={RelativeSource Self}}” />
Here we’ve set Stretch to Uniform and bound the Width and Height to the PixelWidth and >PixelHeight of the Source, effectively ignoring DPI. The image however will not be pixel >perfect, even when using SnapToDevicePixels (which simply snaps the borders, not pixels >within the image). However, WPF in 3.5 SP1 will support a NearestNeighbor >BitmapScalingMode, which should correct this.
Use a DPI of 96. WPF is scaling your image based on the size in inches, while the image viewer is displaying pixels. On most Windows systems, the screen resolution is assumed to be 96 DPI, so using that in your image will result in a one-to-one translation.
Alternatively, you could extend Image and implement MeasureOverride and ArrangeOverride to change the effect of the image's DPI:
class DpiAgnosticImage : Image
{
protected override Size MeasureOverride(Size constraint)
{
var bitmapImage = Source as BitmapImage;
var desiredSize = bitmapImage == null
? base.MeasureOverride(constraint)
: new Size(bitmapImage.PixelWidth, bitmapImage.PixelHeight);
var dpiScale = MiscUtil.GetDpiScale(this);
desiredSize = new Size(desiredSize.Width / dpiScale.Width, desiredSize.Height / dpiScale.Height);
desiredSize = ImageUtilities.ConstrainWithoutDistorting(desiredSize, constraint);
if (UseLayoutRounding)
{
desiredSize.Width = Math.Round(desiredSize.Width);
desiredSize.Height= Math.Round(desiredSize.Height);
}
return desiredSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
return new Size(Math.Round(DesiredSize.Width), Math.Round(DesiredSize.Height));
}
}
Use it in xaml as if it were an Image:
<Grid>
<local:DpiAgnosticImage
Stretch="None"
Source="{Binding ViewImage}">
<Image.RenderTransform>
<ScaleTransform
x:Name="SomeName"/>
</Image.RenderTransform>
</local:DpiAgnosticImage>
</Grid>
Flaws to above code (that I know of):
Ignores Stretch
Assumes Source is a BitmapImage
=== Edit - Will's comment suggests he would like to know what is in GetDpiScale()
public static Size GetDpiScale(Visual visual)
{
var source = PresentationSource.FromVisual(visual);
var dpiScale = new Size(
source.CompositionTarget.TransformToDevice.M11,
source.CompositionTarget.TransformToDevice.M22);
return dpiScale;
}
This is the result of the .jpg file itself specifying the DPI - WPF is simply obeying. Here is a forum post detailing the problem with some solutions:

WPF Image Dynamically changing Image source during runtime

I have a window with a title on it. When the user selects a choice from a drop down list, the title image can change. The problem is when the image loads, it's a blurred, stretched, and pixelated. These are PNG files I'm working with and they look good prior to setting the source dynamically.
Here's the code I'm using to change the image's source.
string strUri2 = String.Format(#"pack://application:,,,/MyAssembly;component/resources/main titles/{0}", CurrenSelection.TitleImage);
Stream iconStream2 = App.GetResourceStream(new Uri(strUri2)).Stream;
imgTitle.Source = HelperFunctions.returnImage(iconStream2);
Here are the helper functions.
public static BitmapImage returnImage(Stream iconStream)
{
Bitmap brush = new Bitmap(iconStream);
System.Drawing.Image img = brush.GetThumbnailImage(brush.Height, brush.Width, null, System.IntPtr.Zero);
var imgbrush = new BitmapImage();
imgbrush.BeginInit();
imgbrush.StreamSource = ConvertImageToMemoryStream(img);
imgbrush.CreateOptions = BitmapCreateOptions.PreservePixelFormat;
imgbrush.EndInit();
var ib = new ImageBrush(imgbrush);
return imgbrush;
}
public static MemoryStream ConvertImageToMemoryStream(System.Drawing.Image img)
{
var ms = new MemoryStream();
img.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
return ms;
}
And the XAML
<Image x:Name="imgTitle" HorizontalAlignment="Left" VerticalAlignment="Bottom" Grid.Column="1" Grid.Row="1" Stretch="None" d:IsLocked="False"/>
And for Ref:
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
Anyone have any ideas what's up?
I can think of two things:
First, try loading the image with:
string strUri2 = String.Format(#"pack://application:,,,/MyAseemby;component/resources/main titles/{0}", CurrenSelection.TitleImage);
imgTitle.Source = new BitmapImage(new Uri(strUri2));
Maybe the problem is with WinForm's image resizing, if the image is stretched set Stretch on the image control to "Uniform" or "UnfirofmToFill".
Second option is that maybe the image is not aligned to the pixel grid, you can read about it on my blog at http://www.nbdtech.com/blog/archive/2008/11/20/blurred-images-in-wpf.aspx
Hey, this one is kind of ugly but it's one line only:
imgTitle.Source = new BitmapImage(new Uri(#"pack://application:,,,/YourAssembly;component/your_image.png"));
Here is how it worked beautifully for me.
In the window resources add the image.
<Image x:Key="delImg" >
<Image.Source>
<BitmapImage UriSource="Images/delitem.gif"></BitmapImage>
</Image.Source>
</Image>
Then the code goes like this.
Image img = new Image()
img.Source = ((Image)this.Resources["delImg"]).Source;
"this" is referring to the Window object
Like for me -> working is:
string strUri2 = Directory.GetCurrentDirectory()+#"/Images/ok_progress.png";
image1.Source = new BitmapImage(new Uri(strUri2));
Me.imgAddNew.Source = New System.Windows.Media.Imaging.BitmapImage(New Uri("/SPMS;component/Images/Cancel__Red-64.png", UriKind.Relative))
Try Stretch="UniformToFill" on the Image

Resources