I want to optimize the memory usage in my WPF app.
I want to load from the disk an jpg image and show it with its real size.
And then I want to show 5 cropped square section of the original image loaded from the disk.
(No resize is applied on any image).
I want to do all this by loading the original image once and sharing that data among the image controls, in such a way that no memory is wasted and all controls fetch data from the same memory location.
I tried by using a memory stream object but in the end due to some convertions between bitmap and bitmapimage I ended up copying the data.
I found an interesting way to crop an image from a BitmapImage here that I think will solve your problem. Doing it this way, you can display all of your images just using a single BitmapImage
In your xaml your full resolution image would just be a regular image element, but your cropped images would be a rectangle element using an image brush with a specific viewbox. Just define the rectangle with the height and width of the cropped image that you want, and then the viewbox is defined as "x y width height" (in my example it is "10 20 100 200") and remember that x starts at 0 for left and is positive moving right, and y starts at 0 for the top and is positive moving down.
<Image Source="{Binding Image}"></Image>
<Rectangle Height="200" Width="100">
<Rectangle.Fill>
<ImageBrush ViewboxUnits="Absolute" Viewbox="10,20,100,200" ImageSource="{Binding Image}"></ImageBrush>
</Rectangle.Fill>
</Rectangle>
Note that the binding for the Image and the ImageBrush are the same, so you only need to define Image once, and it is used across both elements.
Related
My application detects a foreign object (blob, cluster etc) in the live webcam image and displays object's outline on top of the image. To achieve that I employ Image and Canvas elements as follows:
<Border x:Name="ViewportBorder" Grid.Column="0" Grid.Row="1" BorderThickness="3" Background="AliceBlue" BorderBrush="Red">
<Grid>
<Image x:Name="videoPlayer" Stretch="Uniform" MouseDown="videoPlayer_MouseDown"></Image>
<Canvas x:Name="ObjectsCanvas"></Canvas>
</Grid>
</Border>
Border element in the above XAML is used just to draw a thick red line border around the Grid containing videoPlayer and ObjectsCanvas. Stretch="Uniform" is set to preserve image aspect ratio while being able it to stretch when application window gets maximized.
Every time the new frame arrives from the camera videoPlayer.Source gets updated with frame's bitmap whereas blob detection method yields a list of coordinates used for drawing a Polyline. The Polyline object is then added to ObjectsCanvas to be shown on top of the actual image frame.
Here's a part which draws the blob and adds it to the ObjectsCanvas.Children:
private void DrawBlob(List<Point> corners)
{
this.Dispatcher.Invoke(() =>
{
var myPolyline = new Polyline();
myPolyline.Stroke = System.Windows.Media.Brushes.Yellow;
myPolyline.StrokeThickness = 4;
myPolyline.FillRule = FillRule.EvenOdd;
myPolyline.Points = corners;
Canvas.SetLeft(myPolyline, 0);
Canvas.SetTop(myPolyline, 0);
ObjectsCanvas.Children.Clear(); // remove any old blob polyline
ObjectsCanvas.Children.Add(myPolyline); // add new polyline
});
}
When running the application I observe imperfect overlap of the blob object (thick yellow polyline), it gets somewhat right-shifted as shown in the image below.
Observed imperfection is not due to blob detection algorithm! I verified that by drawing the polylines of very same coordinates using old-fashion GDI methods on the actual bitmap.
It gets worse when I maximize the application window, an action causing videoPlayer to stretch:
I tried setting HorizontalAlignment and VerticalAlignment properties of ObjectsCanvas to Stretch but that does not help. Is there any method to align canvas exactly with the actual displayed image region?
I could get back to drawing the polylines using GDI, but I think it's a shame doing so in WPF...
I think I found a solution.
So, in order to stretch your canvas up to your image size you could wrap your canvas in ViewvBox control and bind your Canvas.Height and Canvas.Width to the image source's Height and Width like so:
<Grid>
<Image x:Name="MyFrame" Stretch="Uniform" />
<Viewbox Stretch="Uniform">
<Canvas
x:Name="MyCanvas"
Width="{Binding ElementName=MyFrame, Path=Source.Width}"
Height="{Binding ElementName=MyFrame, Path=Source.Height}">
<Canvas.Background>
<SolidColorBrush Opacity="0" Color="White" />
</Canvas.Background>
</Canvas>
</Viewbox>
</Grid>
However you need to set your Canvas.Background (you can make it transparent like in the example above), or ViewvBox will hide all of the canvas children otherwise.
Here are some screenshots of it working with a yellow polyline:
One more thing to note here - the coordinates of your polyline should be relative to image resolution.
Hope that helps!
Since the creators update came out, uwp can use svg images as briefly explained here (minute 3).
I have this svg (48x48) and i can use it fine, if (and only if) i set the image's width&height to 48 and the strech to none:
<Image Source="ms-appx:///Assets//check.svg" Height="48" Width="48" Stretch="None"/>
If i set the stretch to fill, the image disappears. If i increase the width and height i can see that the icon is pinned to the upper left corner of the image (screenshot with a different svg but same size). Isn't Stretch=Fill and a fixed height/width the intended way to scale an image?
It looks to my as if the stretching algorithm does not grasp that my svg is supposed to be 48x48. Am i doing it wrong, or are there workarounds?
Okay, so here is how I solved this!
YouTube Video for this!
Open the SVG file
The SVG file Width and Height - set these to auto!
I've been having the same issue all morning and was about to completely give up on Svg support, seems mad that you can't get a scalable format to scale properly...
But, I had one more go and I think I've worked this out.
It seems that you need to tell the SvgImageSource to rasterize at the SVG's original design size and then get the Image to scale it. Not sure it's a particularly helpful approach, but certainly solves it as of build 15063.
<Image Width="24" Stretch="Uniform" VerticalAlignment="Center">
<Image.Source>
<SvgImageSource UriSource="ms-appx:///Assets/salesorder.folder.plain.svg"
RasterizePixelHeight="48"
RasterizePixelWidth="48" />
</Image.Source>
</Image>
So if the SVG was 48x48 we turn it into a bitmap at 48x48 using the RasterizePixelHeight and RasterizePixelWidth values and then the image scales that to 24x24.
Hope that helps.
Update
I just re-read your question and realised that you were looking to increase the scale whereas I've been working to decrease it. Looks as though the technique still works, but as you scale up you're going to lose any sharpness of image due to the bitmap scale process. I think this points to a fundamental flaw in the current implementation. They seem to be rendering the svg as a bitmap, and we have to tell it the original size to get it to render properly, and then they allow the bitmap code to do the rest of the work.
I think it's somewhat debateable whether this is true support or an odd half way house. Somewhere someone suggested that Adobe Illustrator can generate XAML paths, I think I'm going to look at that to see whether I can get a better quality output, shame though because I really like Inkscape :-(
For me, it worked with modifying SVG file like this:
Add appropriate preserveAspectRatio property to svg tag. For me it was "xMinYMin meet".
Set viewbox property of svg tag to this template "0 0 ActualHeight ActualWidth", in my case it was "0 0 1050 805".
Set height and width of svg tag to "auto".
Now svg element is relative to Height, Width and Stretch properties you provide in your XAML page or view.
It might be needed to rebuild the project for XAML Designer to take effect.
SVG File:
<svg preserveAspectRatio="xMinYMin meet" viewBox="0 0 1050 805" width="auto" height="auto" ... > ... </svg>
XAML File:
<Image
Width="200"
Source="ms-appx:///Assets/Photos/Illustrations/sample.svg"
Stretch="UniformToFill" />
For me it works only if you set RasterizePixelHeight and RasterizePixelWidth to the svg original resolution (e.g. document properties in Inkscape).
For me it worked without setting those Properties, but adding preserveAspectRatio="xMinYMin" to the <svg ...> tag and deleting width and height from the <svg ...> tag.
This question already has answers here:
How to draw directly on bitmap (BitmapSource, WriteableBitmap) in WPF?
(2 answers)
Closed 5 years ago.
Instead of making various draw and bitmap blit calls to a canvas, how would I do it to a backscreen bitmap? The bitmap would be exactly the same size as the canvas and I want to draw and blit to the exact same coordinates.
So, this is a mapmaking program. Below is a screen capture of a series of PNGs (representing trees) that are added as children to the canvas:
Not only are they added to the canvas I also want to copy all of them to a backscreen (presumably a bitmap). This will be used for an undo function. Other various drawing calls (like swamp, roads, etc.) will also be copied to the backscreen and eventually to another bitmap and, eventually, saved to disk as the finished map.
Not really sure what you mean, but a fixed background for a canvas could be achieved in wpf like this. You could also feed the ImageBrush from Code via binding.
<Canvas x:Name="LayoutRoot" Margin="485,24,0,0" HorizontalAlignment="Left" Width="800" Height="600" VerticalAlignment="Top">
<Canvas.Background>
<ImageBrush ImageSource="../yourBackgroundImage.jpg" Stretch="None" />
</Canvas.Background>
</Canvas>
Hope that helps. If I did not understand your question correctly please clarify and I am happy to revisit.
I read this link How to TILE a background image in WinRT?
I need to have a background image for example 300*1000 pixel i have base image in 300*10 pixel and i want to repeat this base image for the background
i use this line and really TileMode is not in this version of Metro/XAML.
<ImageBrush ImageSource="ms-appx:///Assets/Note.png" Stretch="None" AlignmentY="Top" x:Name="my_image" >
i want to have to repeat "my_image" for 100times!
have can i do that? is there any idea?
thnak for your attention
I'm trying to display an image on a splash screen and it's being stretched upon display. The image I'm trying to display is a simple bmp file. Any ideas why?
In SplashWindow.xaml:
<Window ... SizeToContent="WidthAndHeight">
<Grid>
...
<Image Grid.Row="0" Source="{Binding SplashImage}"></Image>
</Grid>
</Window>
In SplashViewModel.cs
public ImageSource SplashImage
{
get
{
return ImageUtilities.GetImageSource(_splashImageFilenameString);
}
}
From ImageUtilities.cs
public static ImageSource GetImageSource(string imageFilename)
{
BitmapFrame bitmapFrame = null;
if(!string.IsNullOrEmpty(imageFilename))
{
if(File.Exists(imageFilename))
{
bitmapFrame = BitmapFrame.Create(new Uri(imageFilename));
}
else
{
Debug.Assert(false, "File " + imageFilename + " does not exist.");
}
}
return bitmapFrame;
}
In your XAML, set the "Stretch" property to "None" (I believe it defaults to "Fill"):
<Image Grid.Row="0" Source="{Binding SplashImage}" Stretch="None"></Image>
You can also explicitly set the Width and Height properties if you like.
typically you want:
<Image Source="{Binding ImagePath}" Stretch="Uniform" />
this value will enlarge the image as much as possible while still fitting entirely within your parent control. It will not distort it, it will maintain the source's aspect ratio. If you use
Stretch="None"
it will display the image (or what fits of the image, it will clip) at it's native size which is not always what you want.
Anyhow, you have some choices but setting Stretch to what you want will effect the way the image stretches or not.
WPF doesn't display things in pixels (at least not on the surface). WPF displays things in device-independent units, specifically 1/96ths of an inch.
Image files have DPI/resolution information in their metadata which tells the computer how big that image is in inches. If your image file has been programmed to say it is 8 inches wide, that's going to be equal to 768 units in WPF, regardless of how many pixels the image is.
You can use an image editing program like Photoshop or equivalent to change the DPI of your image, or just give it an explicit Width and Height when you display it in WPF.